Книга Ю.А. Зальцмана, опубликованная в журнале «Персональный компьютер БК-0010, БК-0011М» №№1-5 за 1994 г., 1-3 за 1995 г.
Ю.А. Зальцман,
г. Алма-Ата
МИКРО-ЭВМ БК-0010. АРХИТЕКТУРА И ПРОГРАММИРОВАНИЕ НА ЯЗЫКЕ АССЕМБЛЕРА
«В начале было слово...» Точнее, не слово, а множество вопросов пользователей БК-0010 к автору данной статьи, на самые различные темы, касающиеся программирования и архитектуры этой микро-ЭВМ. Сначала вопросы по поводу программирования на языке Фокал, затем - на языке Бейсик, а ещё позже - по программированию в машинных кодах, или на языке ассемблера. Если число вопросов по Фокалу и Бейсику было относительно невелико и быстро пошло на убыль (это, видимо, объясняется тем, что эти языки достаточно полно описаны в прилагаемой к БК-0010 документации и различных периодических изданиях), то вопросы по программированию в машинных кодах не прекращаются и по сей день.
Введение
Что же представляет собой наш самый массовый компьютер? О БК-0010 писали много. Писали в специальной литературе и в газетах хорошо и плохо, профессионалы и дилетанты. Среди лиц, избалованных мощной импортной техникой, распространено презрительное отношение к БК, как к «большому калькулятору», «уродцу в семействе ЭВМ» и т.п. Но создаётся впечатление, что подобные отзывы исходят в основном от людей, либо совсем не знакомых с БК, либо пытавшихся с ней работать, но не имеющих навыков программирования и вследствие этого просто не справившихся с этим «калькулятором». Такие люди привыкли к тому, что ЭВМ всё делает за них сама, а на дискетах имеется полный набор первоклассных фирменных программ - графические и текстовые редакторы, трансляторы языков высокого уровня, базы данных, электронные таблицы... Пользователю надо лишь освоить работу в операционной системе, и ему становятся доступны все возможности компьютера. Можно составлять каталог своей личной библиотеки или готовить статью в «Литературную газету» о том, какая это плохая ЭВМ - БК-0010 и какие плохие люди её делают...
Но прервём разбег фантазии. Все мы знаем, что хороший компьютер лучше, чем плохой. А БК-0010, что это всё-таки такое? И так ли уж это плохо?
Уважаемый читатель, давайте чуть отвлечёмся от темы. Вам нравятся микро-ЭВМ семейства ДВК? ДВК-1, ДВК-2, ДВК-3, наконец, ДВК-4? Если вам приходилось работать с этой техникой, ответ, скорее всего, будет либо положительный, либо нейтральный - дело вкуса. Но никто, конечно, не скажет, что ДВК-2М, например, - это большой калькулятор. А теперь внимание: БК-0010 - полный аналог ДВК, и не только по системе команд, но и по многим другим параметрам! БК-0010, как и ДВК, изготовлен на базе микропроцессорного комплекта К1801, имеет такой же центральный процессор (К1801ВМ1), практически такое же быстродействие и адресное пространство. Можно ли сказать, что БК-0010 - это, с точки зрения пользователя, практически ДВК-1? Не совсем. БК-0010 лучше!
Да, в нём ОЗУ пользователя всего 16 Кбайт. Но зато экранное ОЗУ входит в общее адресное пространство и с экраном можно работать, как с ОЗУ пользователя. Вы удивлены и не понимаете, что это за преимущество? Достаточно отметить, что это позволяет очень легко и просто организовать работу ЭВМ в графическом режиме. Но мало того, очень многие задачи (начиная с компьютерных игр и кончая «системами распознавания образов») позволяют использовать экранное ОЗУ БК-0010 как часть ОЗУ пользователя и решать задачи прямо на экране, не занимая основную память ЭВМ.
В БК-0010 реализована чёрно-белая графика довольно высокого разрешения - 512 х 256 точек, что в 2,5 раза выше, чем на столь популярной за рубежом ЭВМ «Синклер», и даже чуть выше, чем на первых мониторах (CGA) IBM PC. Заметим также, что на ДВК-1 и ДВК-2 графического режима нет вообще! Кроме того, БК-0010 имеет цветной режим, позволяя выводить на экран цветного монитора четыре основных цвета - красный, зелёный, синий и чёрный, причём программным путём можно легко увеличить число цветов до 8-16.
БК-0010 не имеет дисковода. Это серьёзный минус, но это и плюс! Отсюда его относительно низкая стоимость и высокая надёжность. Монитор-драйверная система (МДС, или та же ОС) БК-0010 записана в постоянных запоминающих устройствах (ПЗУ) компьютера и поэтому не требует загрузки извне. Она не «зависает», не даёт сбоев, нечувствительна к «вирусам». В состав ОС (МДС) БК-0010 помимо основных программ, обеспечивающих работу ЭВМ, её связь с внешними устройствами, и т.д., входят два интерпретатора языков высокого уровня - вильнюсский Бейсик (по возможностям близкий к BASIC-MSX микро-ЭВМ «Ямаха-2») и Фокал - очень простой и гибкий язык, предназначенный для решения научно-технических задач, к сожалению, до сих пор малознакомый программистам. Фокал БК-0010 имеет расширенный набор средств, в том числе графические операторы, и вполне может потягаться с Бейсиком по своим возможностям. Все эти средства доступны сразу после включения ЭВМ! Вам даже не нужно устанавливать в дисковод дискету и обращаться к помощи ОС.
Есть в БК-0010 и так называемая мониторная система диагностики - МСД. Она не только даёт возможность проверить работоспособность и исправность ЭВМ, но и содержит средства, позволяющие писать, запускать и отлаживать программы непосредственно в машинных кодах. БК-0010 имеет довольно большой (для бытового компьютера) набор шрифтов и псевдографических символов. может выводить на экран дисплея два формата шрифта - 64 и 32 символа в строке...
Опытный пользователь персональных ЭВМ уже давно, наверное, с усмешкой читает этот панегирик БК-0010. У него готов вопрос: «А прикладное программное обеспечение? Ведь известно, что за рубежом его стоимость обычно в 5-10 раз превышает стоимость "железа", т.е. самого компьютера! А чем может похвастать БК?» Может, уважаемый оппонент, и очень многим! На сегодня для БК-0010 разработано свыше 6000 прикладных, системных и инструментальных программ. На БК адаптированы языки: Бейсик (около 10 версий), Форт (3-4 основных версии), Си (2 основные версии), Т-язык. Разработан совершенно новый специализированный язык- транслятор ALMIC. На БК имеется не менее 40 версий редакторов текста, столько же, если не больше, графических редакторов, очень удобные и мощные ассемблер-системы. И, конечно, игры. Любые - логические (в том числе Шахматы), динамические, игры-приключения, с графикой и текстовые, с объёмными изображениями и звуковыми эффектами, чёрно-белые и цветные, полиэкранные, многосерийные и всякие другие, какие только можно вообразить. Ну а что касается математических программ (статистическая обработка данных, вычисления по формулам и т.п.), то их такое множество, что автор не решается даже перечислить их основные классы. Многие из них представляют результаты обработки данных и вычислений не только в цифровом виде, но и графически, в том числе в цвете. А как с базами данных? Имеется несколько простых версий, но есть и СУБД, аналогичная dBASE IBM PC. Есть специальные программы сортировки по алфавиту, драйверы для печати на принтерах разных систем, от Консул-254 до EPSON и ThinkJet, и электронные таблицы. Есть программы упаковки и вывода изображений и текстов, уплотнения записей на магнитной ленте (МЛ) и даже синтезатор речи с неограниченным словарём русского языка!
Однако тут самое время остановиться, ведь даже простое перечисление имеющихся для БК программных средств займёт не одну страницу, а может быть, даже не одну книгу! Пусть-ка лучше автор ответит на вопрос: где эти программные средства взять?
Что же, вопрос резонный. Почти все перечисленные средства написаны не профессиональными программистами, а любителями. Но в большинстве случаев это ничуть не снижает их качество. Ведь что такое профессиональный программист (если, конечно, это действительно программист, а не ремесленник)? Это просто любитель, получающий зарплату за свой труд. Будем смотреть на вещи трезво - в нашей стране почти никто из так называемых профессиональных программистов не имеет специального программистского образования... И почти никто из имеющих такое образование программистом не работает! Эта в высшей степени парадоксальная ситуация, видимо, объясняется тем, что программирование скорее призвание, талант, чем банальная профессия.
Ассортимент разработанных для БК-0010 программ довольно широк, и многие из них сделаны на высоком профессиональном уровне. Почему же, несмотря на это, до сих пор наблюдается острый дефицит программных средств? Дело в том, что в СССР так и не были приняты законы, защищающие авторские права программиста, в связи с чем авторы не заинтересованы были в распространении своих программ. (Закон Российской Федерации о правовой охране программ для ЭВМ и баз данных принят лишь 23 сентября 1992 г. Полный текст этого документа опубликован в журнале «Информатика и образование» №1 за 1993 г. - Прим. ред.) Нет до сих пор и единой системы распространения программных средств, единых расценок и гарантий оплаты. Многие государственные и кооперативные предприятия, торгующие программным обеспечением, делают это без всякого согласия авторов и не выплачивают им вознаграждения. Понятно, что при этом авторы даже препятствуют распространению программ, а те из них, которые тиражируются, зачастую представляют собой устаревшие версии или имеют ошибки, в том числе и намеренно внесённые. Однако уже сегодня есть целый ряд организаций, тиражирующих программы по договорам с авторами и предлагающих пользователям за весьма умеренную плату лучшие образцы творчества программистов для БК-0010. Пользователю нужно только следить за рекламой в журналах.
Теперь, после этого несколько затянувшегося, но, по-видимому, необходимого вступления, перейдём к вопросу: чего же всё-таки не хватает БК-0010? Прежде всего, конечно, памяти. Хотя в настоящее время разработано уже несколько подходов для расширения ОЗУ и ПЗУ БК-0010, вряд ли можно ожидать, что их применение станет массовым, пока промышленность не наладит серийный выпуск таких «расширителей». Кроме малого объёма ОЗУ пользователи БК-0010 также жалуются на её низкое быстродействие. Но прошу минуту терпения! Обе эти жалобы относятся в основном к программированию на языках высокого уровня. Действительно, Фокал - весьма медленный язык, а вильнюсский Бейсик оставляет желать лучшего в распределении памяти и оптимальности написания программ. Однако есть способ, позволяющий как резко поднять скорость исполнения программ, так и в большинстве случаев существенно экономить память микро-ЭВМ. Это переход к программированию в машинных кодах, или. что по существу одно и то же, на языке ассемблера. Кроме общих преимуществ ассемблера на любой ЭВМ, система команд БК соответствует принятой для компьютеров фирмы Digital Equipment Corp. и имеет существенные преимущества перед стандартом для компьютеров фирмы IBM. Достаточно сказать, например, что, недавно реализованная на БК игра BLOCK OUT (объёмный Тетрис), практически не уступающая аналогичной игре на IBM PC, занимает менее 16 Кбайт (на IBM PC - около 200 Кбайт!).
Когда можно считать, что пользователь БК-0010 «созрел» для перехода на ассемблер? Прежде всего тогда, когда он ставит перед собой задачу, неразрешимую средствами Бейсика или Фокала. Затем он должен знать основы информатики и программирования и уметь программировать хотя бы на одном из языков, имеющихся на БК-0010 или другой ЭВМ. При невыполнении последнего условия осваивать программирование на языке ассемблера будет очень трудно. Далее, как уже было сказано выше, нужны некоторые способности (но не большие, чем для программирования на Бейсике), а главное - желание и терпение, необходимые для всякого дела, а для программирования на ассемблере в особенности, ибо первые практические результаты будут получены не так уж скоро.
В данной статье автор ставит перед собой задачу, во-первых, дать читателю возможность досконально изучить архитектуру БК-0010, а во-вторых, познакомить его с основами программирования на самом гибком и мощном из языков любой ЭВМ - языке ассемблера.
По возможности будут пояснены также некоторые общие вопросы информатики и устройства ЭВМ. Ограниченный объём публикации, к сожалению, не позволяет вдаваться в подробности. Приходится также учитывать различный уровень подготовки читателей: одним, например, вопросы схемотехники компьютера совершенно недоступны, другим скучно изложение основ информатики, с которыми они давно знакомы... Поэтому предполагается, что эти общие (или, наоборот, узкоспециальные) вопросы читатель может изучить самостоятельно, с помощью имеющейся обширной литературы, в зависимости от своего желания и возможностей. Автор старался сделать изложение материала как можно более живым и доступным пониманию, что иногда вынужденно приводило к некоторой нестрогости изложения, расплывчатости формулировок и прочим дефектам, избежать которых полностью, конечно, не удалось. Но при написании статьи и не ставилась целью подготовка профессиональных программистов, а излагаемый материал отнюдь не претендует на роль истины в последней инстанции.
Рекомендуемая литература:
- Руководства и пособия, входящие в комплект БК-0010 (разные предприятия, выпускающие БК, комплектуют его различным набором литературы и под несколько отличающимися названиями, но примерно одного содержания).
- Вигдорчик Г.В., Воробьёв А.Ю., Праченко В.Д. Основы программирования на ассемблере для СМ ЭВМ. М.: Финансы и статистика, 1983.
- Сингер М. Мини-ЭВМ PDP-11: программирование на языке ассемблера и организация машины. М.: Мир, 1984.
- Фролов Г.И., Гембицкий Р.А. Микропроцессоры. Вып. 7. Автоматизированные системы контроля объектов. М.: Высшая школа, 1984.
- Брусенцов Н.П. Микрокомпьютеры. М.: Наука, 1985.
- Фрэнк Т.С. PDP-11. Архитектура и программирование. М.: Радио и связь, 1986.
- Брябрин В.М. Программное обеспечение персональных ЭВМ. М.: Наука, 1988.
- Осетинский Л.Г., Осетинский М.Г., Писаревский А.Н. Фокал для микро- и мини-компьютеров. Л.: Машиностроение, 1988.
- Талов И.Л., Соловьев А.Н., Борисенков В.Д. Микро-ЭВМ. В 8 кн. Кн. 1. Семейство ЭВМ «Электроника-60». М.: Высшая школа, 1988.
- Фролов Г.И., Шахнов В.А., Смирнов Н.А. Микро-ЭВМ. В 8 кн. Кн. 8. Микро-ЭВМ в учебных заведениях. М.: Высшая школа, 1988.
- Лин В. PDP-11 и VAX-И. Архитектура ЭВМ и программирование на языке ассемблера. М.: Радио и связь. 1989.
- Напрасник М.В. Микропроцессоры и микро-ЭВМ. М.: Высшая школа, 1989.
Данным списком вовсе не исчерпывается вся возможная литература. Просто её перечень так велик, что привести все источники не представляется возможным.
Архитектура БК-0010
Что такое архитектура ЭВМ? По аналогии с архитектурой города можно догадаться, что это - общее построение аппаратных средств машины. Действительно, город, состоящий из улиц и домов, построенных из кирпичей, имеет архитектуру (эти кирпичи расположены определённым образом). С точки зрения жителей города, нас обычно не интересует, что это за кирпичи, их химический состав, размеры и т.п. Зато очень важны расположение районов, ширина улиц, удобство и размеры домов и прочее, с чем мы постоянно имеем дело, живя в городе. Немаловажным вопросом является и городской транспорт. Чтобы узнать, из каких материалов построен дом, его надо разобрать, а это уже дело не его жителей, а строителей или ремонтников. Жителя же интересует обычно только то, что он может увидеть, не прибегая к помощи отбойного молотка. Итак, архитектура ЭВМ - это её аппаратные средства, доступные программным путём. Если программист не может обратиться к каким-либо средствам ЭВМ, то они, по мнению автора, к архитектуре не относятся.
Любая ЭВМ предназначена для обработки информации, причём, как правило, она осуществляет эту обработку опосредованно - представляя информацию в виде чисел. Для работы с числами машина имеет специальную важнейшую часть - центральный процессор (ЦП). Это универсальное логическое устройство, которое оперирует с двоичными числами, осуществляя простейшие логические и математические операции - сдвиг, сложение, сравнение и т.п., и делает это не просто как придётся, а в соответствии с программой, т.е. в заданной последовательности.
Чтобы оперировать с данными, их нужно откуда-то брать, а значит, они должны предварительно где-то храниться. Также где-то должна храниться и необходимая последовательность действий ЦП - программа. Для этого служит запоминающее устройство - ЗУ. ЗУ бывают постоянные (ПЗУ), в которых информация хранится, не изменяясь, сколь угодно долго, и оперативные (ОЗУ), информация в которых может быть в любой момент изменена в соответствии с результатами её обработки. Информация в ПЗУ записывается при их изготовлении, а откуда берётся исходная информация в ОЗУ?
Для ввода информации в ЭВМ служат внешние устройства - клавиатура и накопитель на магнитной ленте (НМЛ). Другое внешнее устройство - дисплей - служит для вывода информации. Отметим, что вывод информации может осуществляться и на НМЛ, после чего она может храниться там неограниченно долго и вновь использоваться. На более мощных ЭВМ существует ряд других внешних устройств: печатающие устройства, или принтеры, накопители на гибких (НГМД) и жёстких (НМД, «винчестер») магнитных дисках и т.п. Все эти возможности «открыты» и для БК, но рассказ о подключении принтеров и дисководов не входил в планы автора данной статьи. (О подключении дисководов читайте в журнале «Персональный компьютер БК-0010 - БК-0011М», №1 за 1993 г. - Прим. ред. )
Как же ЦП общается с ОЗУ, ПЗУ и внешними устройствами? Через так называемое адресное пространство ЭВМ, в котором каждое из внешних устройств (а также каждая ячейка памяти) имеет свой адрес, подобно тому как имеет номер каждый дом в городе. Но мы знаем, что в городах есть и многоквартирные дома, где, войдя в дом, мы ещё не достигаем конечной цели - нужно найти квартиру, а возможно, и позвонить нужное число раз. Или представьте, что, попав в нужный дом, мы обнаружили, что там - почтовое отделение, а в нем своя система адресов и свои правила работы. Есть такие «дома» и в ЭВМ - это регистры внешних устройств, или системные регистры (СР). Обратившись к такому адресу, мы как бы выходим за пределы нашего города, перед нами открыт весь мир! Но чтобы общаться с ним, мы должны знать правила: как написать на конверте адрес, куда конверт опустить, а когда получим ответ - как его понять, вообще, куда он придёт и каким будет.
Такова в общих чертах и архитектура БК-0010. Но мы рассмотрели её очень поверхностно и схематично. Чтобы перейти к подробному рассмотрению, нам придётся чуть отвлечься и разобраться, с какой же информацией и как ЭВМ работает.
Что такое системы счисления, вы. наверное, знаете. Привычная нам десятичная система очень неудобна для ЭВМ - ведь для обработки каждого числа нужно иметь 10 устройств, каждое из которых «знает» одну цифру из десяти - 0, 1, ..., 9. Однако любое число можно выразить и проще - в двоичной системе, где есть всего две цифры - 0 и 1. Правда, при этом запись числа становится очень объёмной и неудобной для человека, а перевести двоичное число в десятичное весьма сложно. Есть компромисс - восьмеричная система, где запись чисел довольно компактна, а перевод в двоичную и обратно очень прост. В этой системе мы и будем общаться с ЭВМ, не забывая, однако, о том. что сама ЭВМ работает только с двоичными числами. Кратко напомним правила перевода двоичного числа в восьмеричное. Для этого нужно разбить двоичное число справа налево на тройки чисел (триады), а затем заменить каждую тройку на соответствующую восьмеричную цифру:
Двоичное число |
Восьмеричная цифра |
---|---|
000 |
0 |
001 |
1 |
010 |
2 |
011 |
3 |
100 |
4 |
101 |
5 |
110 |
6 |
111 |
7 |
Пример:
1010100011111101 |
- исходное число |
|||||
001 |
010 |
100 |
011 |
111 |
101 |
- разбиваем на триады |
1 |
2 |
4 |
3 |
7 |
5 |
- восьмеричное число 124375 |
Заметим, что мы дополнили двоичное число слева двумя незначащими нулями, чтобы получить полную триаду, но это не обязательно. Перевод из восьмеричной системы в двоичную делается в обратном порядке и также несложен.
Все цифровые данные в дальнейшем мы будем приводить в восьмеричном виде, а в тех случаях, когда необходимо использовать десятичное число, будем помечать его буквой «д». Заметим кстати, что восьмеричные числа от 0 до 7 равны десятичным и безразлично, в какой системе они приведены. Если же в составе числа встречаются цифры 8 и 9, то ясно, что число десятичное, и в пометке «д» оно не нуждается. Если нужно будет привести двоичное число, то это будет специально оговорено. Другие системы счисления мы использовать не будем.
Теперь несколько слов о нашем «городском транспорте». Существуют две основные, как их называют, конфигурации ЭВМ - с раздельными шинами адрес-данные и с общей. По названию фирм, разработавших то или иное направление, они носят название INTEL и DEC, соответственно. Не останавливаясь в подробностях на преимуществах и недостатках каждой системы (а они есть и у той, и у другой), скажем лишь, что БК-0010 имеет конфигурацию DEC, или общая шина адреса-данных. Это означает, что и адрес, и данные передаются в ЭВМ по одним и тем же линиям, связывающим её блоки. Но ЭВМ построена так, что программист не ощущает особенностей её конфигурации, и ему не так уж важно, как она работает, а поэтому он может по-прежнему манипулировать адресом и данными отдельно и независимо, не путая их друг с другом. Одним словом, конфигурация ЭВМ (определяемая, кстати, в основном типом ЦП) к архитектуре ЭВМ (в нашем определении) не относится и поэтому нас мало интересует. Продолжая сравнение с городом, можно сказать, что транспорт у нас - только такси. Довольно сесть и назвать адрес, и нас по нему доставят. О маршруте мы можем не беспокоиться, это не наша забота.
Однако в конце «поездки» придётся платить, и на ЭВМ наша «плата» - это время. Если мы укажем адрес не оптимально («в центр - через пригород!»), «счётчик» может «нащёлкать» слишком много, нам не расплатиться - ЭВМ будет слишком долго обрабатывать информацию. Косвенно мы всё-таки учитываем конфигурацию ЭВМ через способы адресации, а как раз они-то в системе команд, рассчитанной на конфигурацию DEC, чрезвычайно гибки и разнообразны. Это и есть основное преимущество «дековской» системы команд. Но обо всём - в своё время.
Контрольные вопросы и задания
- Что такое архитектура ЭВМ?
-
Это аппаратные средства ЭВМ, доступные программным путём.
-
- Какова важнейшая часть ЭВМ, её сокращённое наименование?
-
Центральный процессор (ЦП).
-
- Как сокращённо обозначаются оперативное и постоянное запоминающие
устройства?
-
ОЗУ, ПЗУ.
-
- Какие внешние устройства ЭВМ БК-0010 вы знаете?
-
Клавиатура, накопитель на магнитной ленте, дисплей.
-
- Через какие регистры БК обращается к своим внешним устройствам,
их сокращённое наименование?
-
Регистры внешних устройств, или системные регистры (СР).
-
- Переведите восьмеричное число 3747 в двоичное
(можно пользоваться таблицей, приведённой выше).
-
Порядок перевода:
3
7
4
7
011
111
100
111
Ответ: 11111100111.
-
- Какова конфигурация БК-0010? Как она называется по наименованию
фирмы, разработавшей данное направление?
-
Общая шина адрес-данные (конфигурация DEC).
-
- Какие конкретные преимущества и недостатки конфигураций DEC и INTEL
приводит автор?
-
Конфигурация DEC имеет меньшее число линий связи между элементами системы, что позволяет наращивать разрядность с меньшими затратами. Связь организована по принципу «запрос-ответ», т.е. асинхронно, что позволяет наращивать систему, не заботясь об удалённости её частей друг от друга и об общей синхронизации. Всё это позволяет с большей лёгкостью распараллеливать систему и тем наращивать её мощность и возможности.
Конфигурация INTEL имеет более высокое быстродействие вследствие того, что адрес выставляется по отдельным линиям связи и не требуется дожидаться ответа вызываемого устройства. Но это достигается только тогда, когда вся система размещена на одной плате или в одном корпусе, и время задержки сигнала в проводниках можно не учитывать. Элементы системы конфигурации INTEL заметно проще, чем конфигурации DEC (но с тем же ограничением). Шире ассортимент микросхем, особенно микросхем памяти, рассчитанных на подключение к этой шине.
В общем же обе конфигурации жизнеспособны и продолжают развиваться параллельно. В нашей стране почему-то бытует мнение, что конфигурация DEC не перспективна, - все журналы, посвящённые компьютерной тематике, упорно публикуют материалы только по вопросам, связанным с IBM PC (конфигурация INTEL). Но фирма Digital Equipment Corp. это мнение явно не разделяет, оставаясь вторым в мире после IBM производителем компьютеров, хотя и в основном не персональных.
-
Адресное пространство БК-0010
Войдём в наш «город» - БК-0010. Мы сразу заметим его особенность - в нем всего одна «улица», зато какая! В 65536 «домов»! Это и есть адресное пространство нашей ЭВМ - 65536д байт. А что такое байт? Это блок информации, состоящий из 8 двоичных разрядов, или БИТ. Самое маленькое число, которое может быть записано в одном байте, - это, конечно, ноль (000), самое большое - 255д (377). Переведём последнее число в двоичный формат. Что получилось? Правильно, восемь единиц - 11111111.
Но в нашем «городе» нумерация «домов» немного сложнее. Они как бы стоят в два ряда. Не правда ли, вы и в своём городе такое встречали? Дом 56 и 56а, 78 и 78/1. Только у нас удобнее - каждый «дом» имеет свой номер, причём в «первой линии» стоят только чётные «дома». Если каждый «дом» - это байт, то два байта называются машинным словом или просто словом. Чётные байты имеют, конечно, и чётные номера - 0, 2, ..., 177776, причём номера чётного байта и слова, которому он принадлежит, совпадают. А нечётные? Они - «на задворках», и их номер на 1 больше, чем номер слова. Чётный байт ещё называют младшим, а нечётный - старшим байтом слова, в соответствии с их номерами, ведь номер нечётного байта больше. Зачем так сделано? Дело в том, что во многих случаях мы обращаемся сразу к двум соседним байтам, т.е. к слову, и для этого байты объединены в слова. Но бывает, что нужен и более точный адрес - байт. К нечётному обратиться легко - его адрес отличается от адреса любого слова, а как быть с чётным, адрес которого совпадает с адресом слова? Весьма просто - ЭВМ для обращения к байту (в отличие от обращения к слову) имеет специальные команды, они-то и определяют, что нам нужно не слово, а чётный байт. И ещё особенность - видели ли вы где-нибудь номер дома или квартиры «0»? А у нас в ЭВМ везде и всюду ноль - такое же равноправное число, как любое другое, и даже больше - нумерация всегда начинается с него!
Заметим, что каждый из разрядов (битов) байта или слова (а слове их, понятно, вдвое больше, чем в байте, - 16д) тоже имеет свой номер. Нумерация их идёт справа налево и, как принято, начинается с 0. Правый, или младший, бит слова имеет номер 00, левый, или старший, - номер 15д (для битов, как ни странно, принята десятичная нумерация: традиция, а кроме того, по определённым соображениям это удобно). Можно сказать, что номера битов - это номера «квартир» в наших «домах» - байтах, причём в «домах 2-й линии» (нечётных байтах) они имеют двойную нумерацию - сквозную для слова (8...15д) и собственную для байта (0...7).
Назовём и ещё одну единицу измерения информации, с которой нам придётся встретиться, - килобайт (Кб). Приставка «кило» говорит о том, что килобайт в 1000 раз больше байта, но это только приблизительно! В действительности же 1 Кб = 1024д байта что удобно в двоичной системе счисления, так как именно 1024д, а не 1000 является степенью числа 2.
Итак, наш «город» из одной «улицы» имеет 100000 слов, или 200000 байт, от 0 до 177777. А есть ли в этом «городе» районы? А как же!
Распределение адресного пространства
Когда-то, давным-давно, в незапамятные времена (а точнее 30-40 лет назад), когда ЭВМ только-только появились, а «динозавры» - механические счётные машины - ещё не «вымерли», у первых ЭВМ память была раздельной: в одних определённых её ячейках хранилась программа, а в других - данные, т.е. обрабатываемая информация. Позже от такой организации в большинстве систем отказались, и теперь почти во всех универсальных ЭВМ программа и данные могут занимать любые ячейки памяти во всём адресном пространстве. И всё же отдельные адреса адресного пространства (или, говоря проще, но менее строго, адреса памяти) на любой ЭВМ, как правило, имеют особое назначение. Начнём с нулевого адреса нашего БК и посмотрим, каково распределение его адресного пространства.
Адреса 0...777 - системная область и стек. Это специально выделенные зоны ОЗУ, используемые чаще всего для нужд самой ЭВМ. Более подробно об этом мы поговорим дальше.
Адреса 1000...37777 - ОЗУ пользователя. В этой зоне ОЗУ можно размещать программу и данные - что хотите и как хотите, это исключительное право программиста. Общая длина этой зоны памяти - 15,5 Кб.
Адреса 40000...77777 - экранное ОЗУ. Мы уже говорили, что оно входит в общее адресное пространство и никак от него не отличается. Никак? Но ведь это экран дисплея, и записанная туда информация может быть в любой момент разрушена. Стоит только нажать любую клавишу - на экране появится символ, т.е. информация в зоне экранного ОЗУ изменится, а если нажать клавишу «СБР» - она будет мгновенно уничтожена (экран очищен). Поэтому, используя экранное ОЗУ, мы всегда должны иметь это в виду. Экранное ОЗУ занимает 16 Кб.
Но посмотрим ещё раз на уже известные зоны ОЗУ. Экран занимает так много! Нельзя ли всё-таки использовать хоть часть этой памяти? Можно, и в БК-0010 это предусмотрено. Если нажать клавиши АР2 + «СБР», то экран «сожмётся» до размера 4 строки текста, а программист получит в своё распоряжение дополнительную память, «отрезанную» от экрана. При этом распределение адресного пространства меняется: 1000...67777 - ОЗУ пользователя, а 70000...77777 - экранное ОЗУ. Заметим, что при этом переходе в режим расширения памяти (РП) та часть экранного ОЗУ. которая «отсекается» в пользу ОЗУ пользователя, сохраняет старую информацию, хотя на экране её уже не видно. Напротив, при обратном переходе к большому экрану он очищается и информация, которая была до этого записана по адресам 40000...67777, безвозвратно теряется. Размер экранного ОЗУ в режиме РП - 4 Кб, а ОЗУ пользователя увеличивается на 12 Кб. (Путём использования служебных ячеек системной области БК-0010 можно произвольным образом изменять распределение памяти между ОЗУ пользователя и экранным ОЗУ, однако при этом программа и данные, помещаемые в область, «отрезанную» от экрана, воспроизводятся на нем в виде своеобразной «пестроты». Примером является программа VorteX!. - Прим. ред.)
Следующий большой «район» нашего «города» начинается с адреса 100000 и тянется почти до конца «улицы» - по адрес 177577. Это область ПЗУ. Каковы её особенности? Если в область ОЗУ вы можете записать любую информацию по любому адресу (к чему это приведёт - другой вопрос), то из области ПЗУ вы можете информацию только читать, на запись по этим адресам наложен запрет. Но тогда откуда же взялась та информация, которую мы оттуда читаем? По этим адресам «расположены» специальные микросхемы, в которые информация записана (или, как говорят, «зашита») на заводе-изготовителе, и изменить её нельзя никакими средствами, доступными программисту. Это как бы «наследственная память» нашей ЭВМ, то, что она «знает от рождения». Что же это за знания и как они размещены?
100000...117777 - монитор-драйверная система (МДС). Это. без преувеличения, важнейшая часть ЭВМ, без которой она превращается в мёртвое «железо». Здесь записаны все «основные инстинкты» ЭВМ. Ни одна операция, включая даже ввод символа с клавиатуры и вывод его на экран, не обходится без МДС. Эта часть ПЗУ так тесно связана в обеспечении работоспособности ЭВМ с процессором и содержит столь важные программы функционирования всех систем машины, что иногда её называют виртуальным процессором (здесь мы не станем расшифровывать этот термин).
Далее идёт уже, так сказать, «кора головного мозга». Если без МДС машина мертва, то только с ней она тоже немногое может - загрузить программу, запустить её... Но ни десятичной арифметики, ни чего-либо иного машина «не знает». Она даже не может дать нам возможность «просмотреть» свою собственную память. Вся «надежда» - на программы, загружаемые в ОЗУ! Но у машины есть и другие микросхемы ПЗУ, причём они отличаются для различных типов БК-0010, а в последних моделях БК-0010.01 есть все их виды:
- 120000...137777 - интерпретатор языка Бейсик или языка Фокал.
- 140000...157777 - интерпретатор языка Бейсик или резервное адресное пространство («пустое место»).
- 160000...177577 - интерпретатор языка Бейсик или мониторная система тестов и диагностики (МСД, МСТД).
Уточним, что по техническим причинам одна микросхема ПЗУ БК-0010 включает не более 20000 байт (или 8 Кб) информации, поэтому все ранее перечисленные «части» ПЗУ, кроме последней, имеют эту длину.
Теперь вам ясно, что интерпретатор языка Фокал занимает одну микросхему ПЗУ, а языка Бейсик - три. Ясно также, что Фокал не может работать одновременно с Бейсиком, а Бейсик - с МСД, так как они занимают одни и те же адреса.
Контрольные вопросы и задания
- Каков размер (в байтах) адресного пространства БК-0010?
-
65536 байт
-
- Сколько бит в байте? В слове?
-
8 и 16, соответственно.
-
- Номер какого байта, старшего или младшего, равен номеру слова?
-
Младшего.
-
- Старший байт слова имеет чётный номер?
-
Нет, чётный номер имеет младший байт.
-
- Как машина различает обращение к младшему байту или к слову, имеющим
один и тот же адрес?
-
В зависимости от команды обращения по этому адресу.
-
- В каком направлении нумеруются биты в слове - слева направо или
справа налево? Какой номер имеет младший бит?
-
Справа налево. Младший бит имеет номер 0.
-
- Чему равен килобайт? Его сокращённое обозначение?
-
1024д байта; «К».
-
- Могут ли программа и данные располагаться в памяти произвольно?
От чего это зависит?
-
Да, по желанию программиста.
-
- Нарисуйте распределение адресного пространства БК-0010, изображая
отдельные зоны прямоугольниками и наращивая адреса сверху вниз. Подпишите
их наименования. Укажите адреса начала и конца каждой зоны и её длину
в Кб (при выполнении последнего задания можно воспользоваться текстом
статьи). Сделайте такой же рисунок для режима РП. Выучите распределение
адресного пространства наизусть - это очень важно.
-
Последнюю область, «Область СР», вы имели полное право не изображать, так как мы с ней ещё не знакомы. Но на рисунке приведено всё, «как положено», чтобы к этому вопросу больше не возвращаться.
-
- Какова информационная ёмкость одной микросхемы ПЗУ БК-0010? Может
ли быть изменена записанная туда информация?
-
8 Кб. Не может. Заметим, что микросхемы ПЗУ БК-0010 - так называемые масочные, типа К1801РЕ2, в них информация заносится в процессе изготовления кристалла микросхемы. Существует «легенда», что можно эту информацию переписать заново с помощью специального программатора. Не верьте, дети, сказкам - это неправда. Зато существуют особые перепрограммируемые ПЗУ (ППЗУ), например, типа К573РФЗ, КМ1801РР1. В них действительно можно записать и переписать информацию, но в БК-0010 их нет и никогда не было, они используются в основном при разработке новых ЭВМ. (Однако ППЗУ некоторых типов являются аналогами использованных в БК масочных ПЗУ и могут быть установлены взамен последних без каких-либо изменений в схеме. В одном из выпусков редакция планирует вернуться к этой теме разговора. - Прим. ред.)
-
Мониторная система диагностики (МСД)
Сразу оговоримся, что в состав МСД входит также система тестов нашей микро-ЭВМ.
|
Обычный режим |
|
|
Режим РП |
|
---|---|---|---|---|---|
000000 |
|
|
000000 |
|
|
|
системная область |
0,5К |
|
системная область |
0,5К |
001000 |
|
|
001000 |
|
|
|
ОЗУ пользователя |
15,5К |
|
ОЗУ пользователя |
27,5К |
040000 |
|
|
070000 |
|
|
|
экранное ОЗУ |
16К |
|
экранное ОЗУ |
4К |
100000 |
|
|
100000 |
|
|
|
МДС |
8К |
|
МДС |
8К |
120000 |
120000 |
||||
|
Бейсик или Фокал |
8К |
|
Бейсик или Фокал |
8К |
140000 |
|
|
140000 |
|
|
|
Бейсик или резерв |
8К |
|
Бейсик или резерв |
8К |
160000 |
|
|
160000 |
|
|
|
Бейсик или МСД |
7,875К |
|
Бейсик или МСД |
7,875К |
177600 |
|
|
177600 |
|
|
|
Область СР |
0,125К |
|
Область СР |
0,125К |
ЭВМ - настолько сложное устройство, что проверить её может только она сама! Поэтому и созданы специальные тест-программы, с которыми, конечно, вы знакомы, ведь ваш БК-0010, наверное, проверялся согласно инструкции, как при покупке, так и в дальнейшем. Напомним, как это делается. После включения нажмите клавиши ЛАТ и ЗАГЛ (если у вас БК-0010.01, то перед включением должен быть подключён блок МСТД), а затем нажимайте последовательно: Р, ПРОБЕЛ, Т, ВВОД. На экране появится приглашение тест-системы: «+». Теперь, в зависимости от того, какой из тестов вы хотите провести, нажмите одну из цифровых клавиш 1...5 и выполняйте указания БК. Набор из пяти тестов проверяет все системы микро-ЭВМ исчерпывающим образом (правда, с оговоркой, что алгоритмы некоторых тестов довольно примитивны).
А как перейти в МСД? Мы не зря вспомнили про тесты - выход в МСД производится через тест-систему. Перейдя в тесты, нажмите клавиши: РУС, Т, С. На экране появится приглашение МСД: - «¤». Это так называемый «знак денежной единицы», или, на программистском жаргоне, колесо (иногда его называют также «черепашка», «солнышко»). МСД, как и пусковой монитор (выход в который из Фокала - Р, ПРОБЕЛ, М, ВВОД; из Бейсика - MON, ВВОД; приглашение «?»), также имеет ряд команд, или директив, но количество их и, соответственно, возможности МСД, намного больше, чем в мониторе. Отметим, что МСД ещё называют системой отладки программ в кодах, что само по себе уже кое о чем говорит. Что же может МСД? Удобнее всего отвечать на этот вопрос, изучая её директивы.
Директивы МСД
МСД «понимает» директивы, состоящие из символов (русские заглавные буквы и латинская G) и цифр, причём если в составе директивы присутствуют цифры, то число, следующее до буквы, задаёт какой-либо параметр. Буква без предшествующих цифр соответствующий параметр не задаёт, а индицирует, т.е. выводит на экран какое-либо значение. Помимо директив для работы с памятью ЭВМ в набор команд МСД входят и команды работы с магнитофоном. Все цифровые данные МСД принимает и индицирует только в восьмеричной системе счисления. Эти данные мы будем обозначать как (...). Они могут состоять максимум из шести восьмеричных цифр, причём если их введено больше, то действительны только последние шесть. Незначащие нули слева можно не набирать. Ошибку можно исправить с помощью клавиши «ЗАБОЙ» (перемещение курсора влево со стиранием символа, код клавиши - 30). Итак, директивы:
- (...)А - установить значение текущего адреса. Если мы введём, например. 1000А, то текущий адрес будет равен 1000.
- А - проконтролировать значение текущего адреса. МСД выдаёт ответ «=(...)», в нашем случае: «А=1000».
- (...)Д - установить длину массива (в байтах).
- Д - проконтролировать длину массива. Выполняется аналогично директиве А.
- (...)Р - размножить число (...) в заданном диапазоне адресов, т.е. записать число (...) во все слова с адреса А до А+Д (не включая последний!). Например, если нам нужно «обнулить» (попросту говоря, стереть, или очистить) участок памяти, начиная с адреса 1000 до 4000, мы набираем: 1000А3000Д0Р. Так как запись производится по словам, а А и Д задаются в байтах, то следует задавать лишь чётные А и Д. Иначе используется ближайшее чётное значение, меньшее заданного. (Это замечание относится ко всем случаям, когда параметры задаются в байтах, а работа производится со словом).
- X - подсчитать контрольную сумму массива, размещённого в памяти, начиная с адреса А до А+Д. МСД выдаёт ответ: «=(...)». Например, подсчитаем контрольную сумму ПЗУ МДС: 100000А20000ДХ. Ответ: «=177777», т.е. то же самое значение, которое БК выдаёт для первого ПЗУ, когда мы проводим тест 1. Тут, может быть, уместно пояснить, что это такое. циклическая контрольная сумма вычисляется сложением всех слов (в других случаях - байт) контролируемого массива информации с прибавлением бита переноса за пределы слова (байта). Что такое, в свою очередь, перенос, станет ясно в дальнейшем (правда, ещё не так скоро, но не будем забегать вперёд). Контрольная сумма позволяет убедиться в идентичности массивов, не сравнивая их с эталоном, так как вероятность её случайного совпадения в общем случае не превышает 1/65536 (около 0.0015%), т.е. практически нулевая. Но есть частные случаи, когда контрольная сумма теряет своё ключевое значение. Достаточно привести такой пример: контрольная сумма любого пустого (состоящего из одних нулей) массива всегда равна нулю независимо от его длины. (Иногда по некоторым причинам при чтении файлов с магнитофона БК воспринимает все считываемые биты как нулевые, включая и записанное вместе с файлом значение его контрольной суммы. В этом случае, хотя ошибка чтения очевидна, компьютер считает, что операция выполнена правильно. - Прим. ред.) Разумеется, используемый в МСД способ вычисления контрольных сумм не единственно возможный и не самый лучший.
- (...)П - переслать по адресу (...) массив, записанный, начиная с адреса А до А+Д. Отметим, что «переслать» - не совсем удачный термин, так как исходный массив тоже сохраняется в памяти. Правильнее было бы сказать - «скопировать». Попробуем переслать содержимое какого-либо ПЗУ в экранную область памяти, это выглядит довольно эффектно. Набираем: 100000А20000Д40000П. Содержимое ПЗУ почти мгновенно появляется на экране, что также даёт некоторое представление об «истинном», т.е. не «связанном» языками высокого уровня, быстродействии ЭВМ - ведь ей пришлось при этом переместить в памяти более 4000д чисел и проделать ещё кое-какие операции! Пересылка возможна и с «пересечением» зон адресов пересылаемых массивов, например: 2000А10000Д1000П. Но пересылка «в обратную сторону» (1000А10000Д2000П) не даст ожидаемого результата - оба массива будут испорчены! Если вы вспомните, что пересылка производится по словам, начиная с первого байта массива, то легко догадаетесь, почему так происходит.
- (...)С - сравнить в памяти два массива: эталонный, расположенный с адреса А до адреса А+Д, и контролируемый, с адреса (...). Сравнение производится по словам.
В процессе проверки все расхождения информации в массивах выдаются на экран в следующем виде:
|
(эталонный массив) |
(контролируемый массив) |
||
(1) |
адрес |
данные |
адрес |
данные |
(2) |
адрес |
данные |
адрес |
данные |
|
....... |
...... |
....... |
...... |
(N) |
адрес |
данные |
адрес |
данные |
Если массивы полностью совпадают, на экран ничего не выдаётся. Например, попробуем переслать в память и сравнить с исходным содержимое одного и того же ПЗУ. 100000А20000Д1000П - мы выполнили пересылку по адресу 1000. Теперь намеренно «испортим» массив в ОЗУ уже известной нам директивой: 2000А10Д0Р - мы записали в массив 8 байт (т.е. 4 слова) нулей. Теперь введём: 100000А20000Д1000С. На экран будет выдано четыре строки указанного выше вида - специально «сделанная» нами ошибка в составе контролируемого массива выявлена!
- (...)Л - листать (выдать на экран) массив, начиная с адреса А, длиной (...) байт. После этого А принимает новое значение, равное А+(...). Массив выдаётся на экран в виде машинных слов (а не байт). Например, выполним: 100000А10Л - на экран будет выдано содержимое первых четырёх слов ПЗУ, после чего если мы дадим директиву А, то получим: А=100010.
- И - индикация содержимого слова по текущему адресу.
- (...)И - запись числа (...) в слово по текущему адресу.
- Б - индикация содержимого байта по текущему адресу.
- (...)Б - запись числа (...) в байт по текущему адресу.
Необходимо отметить, что как слово, так и байт выдаются в виде шести цифр. При выдаче байта, конечно, три старшие цифры всегда нули. При вводе байта с клавиатуры может быть набрано любое количество цифр, но запись производится только по модулю 256д (т.е. остатка от целочисленного деления на 256) шести цифр, набранных последними. При байтовых операциях параметр А может быть и нечётным, при этом он определяет старший байт слова с чётным адресом, меньшим на единицу.
- Ц - циклическое (т.е. непрерывное) чтение слова по адресу А. Команда может быть полезна для исследования ячеек памяти с изменяющейся во времени информацией. Выход из цикла - клавиша «СТОП». Например, дайте директиву: 177662АЦ, затем нажимайте различные клавиши и наблюдайте за результатами циклического чтения данной ячейки на экране.
- Щ - снять защиту системной области. Мы уже отмечали, что область адресов 0...777 называется системной и используется преимущественно для собственных нужд ЭВМ. Запись туда случайной информации может грозить неприятностями, вплоть до полного отказа ЭВМ в работе (конечно, временного, до отключения питания или перезапуска ЭВМ, но информация, хранящаяся в ОЗУ, при этом будет утрачена). Чтобы избежать неприятностей, системная область защищена в МСД от случайной записи, и при попытке сделать это выдаётся сообщение «ЗЩ» - защита. После подачи директивы «Щ» защита снимается и возможна запись в системную область. Восстановление защиты - клавиша «СТОП».
- , (запятая) - чтение слова с инкрементом (инкремент - увеличение). По данной директиве текущий адрес А увеличивается на 2, а затем содержимое ячейки по этому адресу выводится на экран.
- (...), - запись слова по текущему адресу с инкрементом: по текущему адресу записывается слово (...), а затем адрес увеличивается на 2 и выводится слово по этому адресу. Эти директивы используются преимущественно для чтения или записи последовательно расположенных ячеек памяти.
- - (минус) - чтение слова по текущему адресу с декрементом.
- (...)- - запись слова (...) по текущему адресу с декрементом (декремент - уменьшение). Данная директива аналогична предыдущей, с той лишь разницей, что значение текущего адреса А не увеличивается, а уменьшается на 2.
- . (точка) - чтение байта с инкрементом.
- (...). - запись байта с инкрементом.
- : (двоеточие) - чтение байта с декрементом.
- (...): - запись байта с декрементом.
Пара последних директив полностью аналогична двум предыдущим, только работают они не со словом, а с байтом, и увеличение или, соответственно, уменьшение текущего адреса при их исполнении происходит не на 2, а на 1. Для этих команд действительны и нечётные адреса.
- МП - пуск мотора магнитофона при наличии дистанционного управления (ДУ) от БК-0010. Останов мотора - любая символьная клавиша или «СТОП». Отметим, что описанная в «Руководстве системного программиста БК-0010» директива «МС» (останов магнитофона) бессмысленна, так как по нажатию первой же клавиши (М) магнитофон остановится и нажимать вторую уже как-то ни к чему, разве что для порядка.
- МФ - фиктивное чтение файлов с магнитной ленты. На запрос «ИМЯ=» нужно ввести имя того файла, после которого магнитофон должен быть остановлен. Загрузка файла в ОЗУ и проверка контрольной суммы по данной директиве не производится, а имена, не совпадающие с заданным, выводятся на экран. (Обычно эта директива используется для просмотра содержимого кассеты при поиске нужного файла. - Прим. ред.)
- МЧ - загрузка файла с МЛ. На запрос «АДРЕС=» необходимо ввести адрес в ОЗУ, начиная с которого должен быть загружен файл, а на «ИМЯ=» - имя загружаемого файла. После набора очередных цифровых данных или имени файла необходимо нажимать клавишу «ВВОД», исправлять ошибки можно клавишей «ЗАБОЙ». Если вместо адреса задать число «0», то файл будет загружен по адресу, указанному в его оглавлении, т.е. в специальном блоке, записанном на МЛ в начале файла. Фактически файл при этом будет загружен в ту область памяти, из которой он был записан на МЛ, а чаще всего именно это и требуется.
- МЗ - запись хранящегося в ОЗУ или ПЗУ массива данных в виде файла на МЛ. На запрос «АДРЕС=» вводится адрес начала массива, «ДЛИНА=» - его длина в байтах. «ИМЯ=» - имя файла, под которым он должен быть записан (имя файла в МСД может состоять не более чем из 16д любых символов; если его длина меньше, оно автоматически дополняется пробелами). Все указанные параметры заносятся в оглавление файла при его записи на МЛ.
Заметим, что при исполнении трёх, последних директив магнитофон с помощью ДУ автоматически включается, а по окончании чтения или записи (или при нажатии клавиши «СТОП») - отключается. Нужно сказать, что ДУ магнитофона при работе с БК - громадное удобство, оценить которое могут лишь те, кто с ним работал. К сожалению, немногие бытовые магнитофоны имеют вход ДУ, совместимый с БК-0010, поэтому большинство пользователей никогда не работали с ним и даже не знают, чего они лишены! (Многие пользователи, имея в своём магнитофоне дистанционное управление, и не подозревают об этом! Так, в некоторых модификациях магнитофона «Электроника-302» ДУ «встроено» в цепь внешнего микрофона и может быть использовано при работе БК, если включить в гнездо микрофонного входа штекер стандартного магнитофонного кабеля БК. - Прим. ред.)
- К - выход из МСД. При этом управление передаётся Фокалу, т.е. по адресу 120000. Содержимое ОЗУ пользователя стирается (кроме зоны адресов 1000...1377).
- ТК - выход в тесты. После выполнения данной директивы появляется приглашение тест-системы: «+». Если не проводить тесты 1 и 5, то содержимое ОЗУ сохраняется. Можно вернуться в МСД. как обычно: РУС, Т, С.
- ТД - переход к тестам внешней диагностики. Применяется при наличии дополнительной диагностической аппаратуры, подключаемой к разъёму системной магистрали (разъем МПИ) БК-0010.
- Т0, Т1, ..., Т5 - запуск тестов МСД. Набор из этих шести тестов несколько отличается от тестов 1...5 тест-системы, но в целом они аналогичны. Отметим, что весьма полезным является тест 2 МСД, а тест 0 может выполняться только при работе ЭВМ в составе локальной сети (в классе КУВТ).
- (...)G - запуск программы по адресу (...). Это единственное исключение, когда директива МСД подаётся в регистре ЛАТ-ЗАГЛ.
Теперь, когда мы ознакомились с директивами МСД, ответим на вопрос, для чего может быть полезна система отладки. Ясно, что в МСД можно как просматривать память ЭВМ, так и изменять её содержимое, т.е. заносить в ячейки памяти информацию. Зная язык машинных кодов (или команд), в МСД можно писать программы в кодах, вводить массивы данных, записывать их в виде файлов на МЛ, запускать, отлаживать.
Хотя язык машинных кодов не так сложен, как принято обычно считать, программировать непосредственно в кодах неудобно, это требует большого напряжения и отличной памяти (имеется в виду, конечно, память программиста, а не БК). Поэтому разработан специальный вспомогательный язык ассемблера, он позволяет достичь тех же самых результатов гораздо меньшей ценой. Зато, имея уже готовую распечатку программы в кодах, можно относительно легко ввести её в память, пользуясь директивами МСД. Это позволяет публиковать тексты сравнительно небольших программ в печатных изданиях, избавляя от необходимости тиражировать их непосредственно на МЛ. Если вам требуется ввести такую программу, то, чтобы избежать ошибок, рекомендуется сделать это дважды по разным адресам, а затем провести сверку этих массивов - маловероятно, что в обоих случаях вы сделаете одинаковые ошибки. Попробуйте ввести в память и запустить простейшую программу в кодах, для чего выполните команды:
1000А 12700, 14, 104016, 12700, 101, 104016, 0, 1000G
На выводимые по директиве «запятая» числа не обращайте внимания и набирайте новые. После запуска эта программа выполняет сброс экрана и выводит символ «А». Как видите, программирование в кодах - не такая уж сложная штука! (Если перед входом в режим МСД из тест-системы выполнить тест 1 (ОЗУ, ПЗУ), то при последующем вводе программы в кодах после нажатия на клавишу «запятая» будет каждый раз выводиться адрес очередной ячейки памяти. - Прим. ред.)
Директивы МСД для работы с магнитофоном, как легко догадаться, позволяют не только загружать программы с МЛ, но и копировать их, если после загрузки записать на МЛ загруженный массив, причём безразлично, на каком языке написана эта программа, нужно лишь знать её адрес и длину. (Кроме некоторых программ с автозапуском или загружаемых в ОЗУ экрана. - Прим. ред.)
В заключение приведём некоторые полезные сведения, которыми можно воспользоваться при работе в МСД.
- Определить параметры успешно загруженного файла можно, если знать, что в ячейке 264 хранится адрес его загрузки, а в ячейке 266 - длина (достаточно дать директиву 264А4Л). Контрольная сумма файла хранится в ячейке 312 и выводится директивой 312АИ. Но можно определить параметры файла (кроме контрольной суммы) и не загружая его, для чего необходимо в режиме фиктивного чтения (МФ) прочитать его имя (независимо от того, совпадает ли оно с заданным), после чего адрес файла содержится в ячейке 346, а длина - в ячейке 350, причём адрес при этом будет «истинный», т.е. указанный в оглавлении на МЛ, а не заданный в процессе загрузки, как это имеет место при чтении ячейки 264. Ячейки 346 и 350 могут быть прочитаны и после загрузки файла с тем же результатом. Отметим, что контрольная сумма в ячейке 312 вычисляется не так, как описанная выше, и поэтому обычно не совпадает с контрольной суммой массива, полученной в МСД по директиве «X».
- По директиве 100274G происходит переход в пусковой монитор (ПМ, символ диалога - «?») без стирания ОЗУ (ПМ мы не описывали, так как он практически всем хорошо известен и значительно уступает МСД по набору директив и своим возможностям). Перейти же из ПМ в МСД можно через тесты, нажав клавиши: Т, ВВОД, РУС, Т, С (также без стирания ОЗУ). Перейти в ПМ без стирания экрана и выхода из режима РП (если он установлен) можно, выполнив директивы: 4АЩ100442И после чего нужно нажать клавишу «СТОП». Смысл того, что при этом делается, вы поймёте в дальнейшем.
- Перейти из МСД в Фокал без стирания ОЗУ можно, выполнив последовательность
директив:
Щ 120020А26Д0П 262А177777И «СТОП»
После этого на экране появляется сообщение Фокала «ОСТАНОВ ПО КЛАВИШЕ СТОП». Чтобы такой переход был успешным, выход из Фокала перед этим должен быть произведён непосредственно в МСД, а не через ПМ, и в МСД не должны загружаться (а тем более запускаться!) программы в кодах. При соблюдении этих условий после выхода в Фокал содержимое ОЗУ полностью сохраняется. Ранее загруженные в Фокале программы могут даже запускаться для дальнейшей работы, возможен просмотр их листинга и т.п. Переход Фокал-МСД-Фокал может делаться таким способом неоднократно, например, с целью модификации в МСД Фокал-программ или для изучения их формата. К сожалению, более подробно останавливаться на этом вопросе нет возможности.
- Выйти из пускового монитора в Бейсик без стирания ОЗУ (с сохранением ранее загруженной Бейсик-программы) можно командой С120234 ВВОД. Правда, ключи клавиатуры (клавиши 1-9 и 0 по регистру АР2) при этом не сохраняются.
Диагностические сообщения МСД
В процессе работы в МСД система ведёт развёрнутый диалог с пользователем, выдавая различные указания и сообщая результаты исполнения директив. Обычно сообщения МСД выдаются в достаточно понятной форме и не требуют особых комментариев, особенно если вы хорошо знаете директивы. Но есть несколько специальных диагностических сообщений, которые весьма кратки и нуждаются в пояснении. С одним из них мы уже познакомились:
- ЗЩ - защита системной области. Выдача этого сообщения означает, что произведена попытка выполнить с помощью одной из директив МСД запись в область адресов 0...777. Напомним, что на запись в системную область наложен запрет (до снятия защиты) исключительно для директив МСД, но не для машинных команд, поэтому от программы пользователя системная область никак не защищена, это забота программиста.
Ещё два сообщения МСД нам пока не встречались, и их следует рассмотреть более подробно.
- 3В(...) - зависание по адресу (...).
Это сообщение выдаётся в двух основных случаях: при попытке записи по адресу ПЗУ и попытке чтения или записи по несуществующему адресу.
Относительно записи информации в область адресов, занимаемую ПЗУ. всё ясно - запись по адресам 100000...177577 запрещена аппаратно и невозможна по техническим причинам. А что такое несуществующий адрес? Это адрес, где нет реального аппаратного устройства, которое могло бы ответить на вызов процессора. При выполнении подобных некорректных операций процессор, не получив ответа от устройства (например, ОЗУ), выполняет так называемое прерывание по зависанию и производит переход по вектору 4 (что это такое, станет ясно в дальнейшем), результатом чего и является выдача сообщения «ЗВ» - зависание и значения текущего адреса А. Понятно, что если зависание возникло в результате исполнения директивы МСД (например, 100000А377И), то выданное значение обычно и есть тот адрес, по которому произошло зависание. Но в прочих случаях, например, при исполнении программы, выданное значение и адрес, по которому произведено некорректное обращение, как правило, не имеют ничего общего. В этом случае информацию несёт только само сообщение «ЗВ».
- НК - неправильная команда. В набор команд процессора входит строго фиксированный перечень кодов. В случае, если код очередной команды, встретившейся в программе, не входит в этот перечень, процессор выполняет прерывание по резервному коду и производит переход по вектору 10 (что также будет в дальнейшем разъяснено). В результате и выдаётся сообщение «НК». Следует особо отметить, что далеко не весь набор команд процессора К1801ВМ1 использован в БК-0010 (или, как говорят, реализован аппаратно). Вследствие этого есть такие коды (не перечисленные, в частности, в «Руководстве системного программиста», прилагаемом к БК), которые вместо ожидаемого сообщения «НК» вызывают сообщение «ЗВ». Примером является код 12. По нему процессор должен был бы произвести запись некоторой информации в область системных регистров. Но нужных регистров в адресном пространстве БК-0010 просто нет, поэтому и происходит прерывание по зависанию. Примером же действительно несуществующей команды является код 30, вызывающий, как и положено, сообщение «НК». Знание этих особенностей поведения процессора позволяет иногда разобраться в загадочных, казалось бы, сообщениях при отладке программы.
Необходимо отметить, что описанные сообщения выдаются только в том случае, если выполняются программы пользователя, загруженные и запущенные в МСД (либо при выполнении директив самой МСД). Если же загрузка и запуск программ производятся из ПМ либо программа пользователя изменяет значение векторов прерывания 4 и 10. диагностические сообщения МСД, разумеется, выдаваться не будут.
Итак, мы вкратце познакомились с мониторной системой диагностики БК-0010, хотя нам и пришлось ради этого надолго прервать описание архитектуры ЭВМ. Но затраченное время в дальнейшем окупится - теперь вы сможете не просто знакомиться с материалом, предлагаемым вашему вниманию, но во многих случаях активно проверять его на своём БК, экспериментировать. Никогда не упускайте случая потренироваться в работе с МСД, а заодно и проверить автора - он ведь тоже может ошибаться!
Контрольные вопросы и задания
- Для чего служит МСД?
-
Для просмотра и изменения содержимого ОЗУ, записи и чтения файлов, написания и отладки программ в кодах.
-
- Как называют знак диалога МСД?
-
Знак денежной единицы, «колесо».
-
- В каком регистре (РУС, ЛАТ, СТР, ЗАГЛ) подаются буквенные директивы
МСД? Какое исключение из этого правила?
-
В регистре РУС-ЗАГЛ, за исключением директивы G.
-
- Выполните следующее задание:
- запишите на МЛ содержимое монитор-драйверной системы БК-0010 (вы помните адрес и длину этого массива?);
- загрузите записанный файл в экранное ОЗУ (с адреса 40000);
- вызовите из ячеек 264, 266, 312, 346, 350 его параметры и поясните, «что есть что».
-
Порядок действий (сообщения МСД частично опущены):
- М3; АДРЕС=100000; ДЛИНА=20000; ИМЯ=МДС;
- МЧ; АДРЕС=40000; ИМЯ=МДС;
- 264А4Л; ответ: 040000 020000 - адрес загрузки и длина файла МДС;
- 346А4Л; ответ: 100000 020000 - «истинные» адрес и длина файла МДС;
- 312АИ; ответ: 017341 - контрольная сумма файла МДС (не совпадающая, как видите, с контрольной суммой в МСД, равной 177777).
- Как перейти из МСД в ПМ без стирания экранного ОЗУ?
-
4АЩ100442 «СТОП».
-
- Предположим, вы загрузили и запустили из режима МСД некоторую программу,
например, ассемблер-систему. При работе с ней (или при запуске написанной
в ней программы) получено сообщение: 3B003474.
О чём это сообщение говорит и почему вы так думаете?
-
Сообщение говорит только о том, что произошло зависание; число 003474 не несёт никакой информации, так как работала программа пользователя, а не директивы МСД.
-
- Постарайтесь выучить директивы МСД и порядок работы с ними наизусть - вам часто придётся ими пользоваться.
Системные регистры
Совершая путешествие по нашему «городу» БК-0010, мы отметили, что «район» ПЗУ тянется почти до конца адресного пространства - до адреса 177577. А что же дальше? С адреса 177600 и до конца «улицы» (по адрес 177777) располагается так называемая область системных регистров.
Системные регистры микро-ЭВМ служат для связи с внешними устройствами, а также для некоторых других целей. Каждый регистр занимает в адресном пространстве одно слово, следовательно, имеет 16д разрядов, с нулевого по пятнадцатый. Простой арифметический подсчёт показывает, что в области адресов 177600...177776 содержится 100 слов, а значит, там можно разместить 100 (64д) регистров. Но далеко не все адреса этой области использованы в нашем компьютере. Рассмотрим имеющиеся в БК-0010 системные регистры в порядке возрастания их адресов. При этом нужно учитывать, что и в имеющихся регистрах далеко не все разряды (биты) используются. Некоторые разряды хранят только определённую, строго фиксированную информацию, которую нельзя изменить, подобно данным, хранимым в ПЗУ. В других информацию меняет только сама ЭВМ, а программист может лишь читать её и использовать результаты (говорят, что такие разряды доступны по чтению). В некоторые разряды регистров можно, наоборот, только записать информацию, но прочитать то, что записано, нельзя (разряды, доступные по записи). Наконец, есть разряды, в которые можно как записывать информацию, так и читать, - они доступны по записи и чтению.
Чем системные регистры (например, доступные по записи и чтению) отличаются от ячеек ОЗУ? Отличие существенное - информацию в ячейках памяти может записывать, читать и использовать только ЦП, он же выводит её на экран по вашей команде. А разряды системных регистров могут быть связаны непосредственно с внешними устройствами - с клавиатурой, магнитофоном, телеграфной линией, - словом, с внешним миром; могут принимать оттуда сигналы или. наоборот, передавать. Есть, правда, регистры, казалось бы, ни с чем не связанные, например таймер (о котором речь пойдёт позже). Но это не более чем заблуждение; таймер тоже связан с внешним миром - через физическое время!
А что же отсутствующие регистры? Это просто резерв для расширения нашей микро-ЭВМ в будущем, так сказать, пустые пока «участки под застройку» (в машинах серии ДВК, а также в БК-0010Ш, работающих в составе комплекса учебной вычислительной техники КУВТ-86, часть из них уже используется).
Итак, приступим к прогулке по последнему «переулку» адресного пространства. Вы готовы? Тогда - в путь!
Напомним, что каждый регистр имеет 16 разрядов, нумеруемых справа налево, причём нумерация начинается с нуля. Номера разрядов - десятичные. По ходу их перечисления будем также указывать, для чего можно использовать тот или иной разряд при программировании (как их использует ЭВМ - другой вопрос).
- Регистр состояния клавиатуры (адрес 177660).
Используются только два разряда:
- Разряд 06 - маска прерываний от клавиатуры. Если бит равен 0 - прерывание разрешено. 1 - запрещено. Доступен по записи и чтению. Можно использовать, чтобы программно «отключить» клавиатуру ЭВМ.
- Разряд 07 - флаг состояния клавиатуры. Устанавливается в 1 при поступлении новых данных в регистр данных клавиатуры (см. ниже), например, при нажатии очередной клавиши, и сбрасывается в 0 при чтении. Доступен только по чтению. Если запретить автоматические чтение из регистра данных, этот разряд может использоваться, чтобы определить, была ли нажата клавиша за истекший промежуток времени. В обычных условиях, когда данные читаются сразу после нажатия клавиши, состояние разряда всегда 0, так как системные драйверы БК реагируют на нажатие клавиши быстрее, чем пользовательская программа.
- Регистр данных клавиатуры (адрес 177662).
Используются семь младших разрядов:
- Разряды 00...06 - буфер кода нажатой клавиши. При нажатии клавиши в него заносится 7-разрядный код, на который не влияют клавиши «НР» («АР2» для БК-0010.01) и «РУС». Полные коды клавиатуры получаются в БК-0010 программным путём. Разряды доступны только по чтению и широко используются для чтения кода клавиатуры независимо от регистра РУС-ЛАТ, для ввода команд без прерывания работы программы (в том числе и на языках высокого уровня) и т.п.
- Регистр смещения (адрес 177664).
Используются 9 разрядов:
- Разряды 00...07 отображают адрес начала экранного ОЗУ, которое организовано по типу рулона, причём их значение указывает количество телевизионных строк дисплея (каждая строка - 100 (64д) байт экранного ОЗУ). В исходном состоянии, когда началу экрана соответствует адрес 40000, содержимое этих разрядов равно 330. По мере сдвига экрана содержимое меняется причём, так как одна строка текста в БК-0010 содержит 12 (10д) телевизионных строк, содержимое регистра также меняется каждый раз на 12. Вверху экрана, как известно, имеется служебная строка, занимающая в экранном ОЗУ 2000 байт (каждая строка обычного текста - 1200 байт). Эти тонкости приводят к тому, что число сдвигов, необходимое, чтобы начало экрана получило снова адрес 40000, не кратно числу строк текста (24д) и содержимое младших разрядов данного регистра практически всё время разное. Разряды доступны по записи и чтению и могут использоваться для плавного сдвига изображения на экране по вертикали. Служебная строка при этом тоже смещается, поэтому данный способ требует специальных приёмов получения изображения.
Нужно отметить, что в режиме «РП» экран организован совсем иначе, чем описано выше, служебная строка (начало экрана) всегда имеет адрес 70000, рулонное смещение не используется, а регистр всегда содержит константу 230.
- Разряд 09 - задание режима расширенной памяти, что соответствует нулю в данном бите. Единица - обычный режим (в «Руководстве системного программиста» ошибочно указано наоборот). Разряд доступен по записи и чтению, может использоваться для обнаружения режима «РП», но включить расширенную память, просто записав туда 0, нельзя (вернее, этого недостаточно)
- Регистры системного таймера (адреса 177706,
177710, 177712).
Эти три регистра не описаны ни в одном руководстве по БК-0010, поэтому
заслуживают подробного рассмотрения.
Прежде всего, что такое системный таймер? Это специальный счётчик, работающий независимо от ЦП. Если запустить такой счётчик, а потом в какие-либо фиксированные моменты времени читать его показания, то по их разности можно определить прошедший отрезок времени.
Итак, наш таймер имеет три регистра. Первый из них - регистр предустановки таймера (адрес 177706) - доступен по записи и чтению, используются все 16 разрядов. Второй - счётчик (177710) - собственно таймер, доступен только по чтению, используются все 16 разрядов. Третий - регистр управления (177712) - доступен по записи и чтению, используется младший байт (разряды 00-07).
Попробуем разобраться, как работает таймер. Если мы подадим определённую команду (запишем в регистр 177712 некоторое число), то в регистр счётчика 177710 будет занесена константа, всегда равная содержимому регистра предустановки, и начнётся отсчёт времени - из константы будет вычитаться по единице до получения в счётчике нуля. Дальнейшее поведение таймера зависит от занесённой в регистр управления команды, а именно от того, какие разряды младшего байта регистра управления установлены в 0 или 1. Рассмотрим далее их назначение.
- Разряд 00 при установке в 1 запрещает счёт и переписывает в регистр счётчика константу из регистра предустановки (режим предустановки).
- Разряд 01 при установке в 1 запрещает фиксацию перехода счётчика через 0 (режим непрерывного счета): досчитав до нуля, таймер продолжает вычитание, в счётчике появляется число 177777, затем 177776 и т.д. Действие разрядов 02 и 03 (см. ниже) при этом, естественно, отменяется, как и повторная перезапись константы из регистра предустановки.
- Разряд 02 при установке в 1 включает индикацию: при очередном переходе таймера через 0 устанавливается в 1 разряд 07 регистра управления, если ранее он был сброшен (режим индикации). Нужно учитывать, что при первом (после включения ЭВМ или «системного сброса») запуске таймера в данном режиме индикация срабатывает только после второго перехода счётчика через 0. Причём независимо от того, работал ли таймер до этого в других режимах.
- Разряд 03 при установке в 1 запрещает повторный счёт: после первого досчёта до 0 устанавливается в 0 разряд 04 (режим однократного счета). Установка данного режима не отменяет режим индикации (при установке в 1 разряда 02).
- Разряд 04 при установке в 1 разрешает счёт (режим счета). В этом режиме при досчёте до 0 в счётчик заново заносится константа из регистра предустановки, следовательно, счёт всегда ведётся от константы до 0 (если только не запрещена фиксация перехода через 0 разрядом 01). При сбросе разряда 04 в 0 в счётчик переписывается константа предустановки.
- Разряд 05 при установке в 1 снижает скорость счета в 4 раза (режим умножения времени на 4).
- Разряд 06 при установке в 1 снижает скорость счета в 16д раз (режим умножения времени на 16). При одновременной установке в 1 обоих разрядов - 05 и 06 - скорость счета снижается в 64д раза.
- Разряд 07 используется для индикации перехода счётчика таймера через 0 (при установленном режиме индикации, см. разряд 02).
Таким образом, возможно множество различных команд таймера и, соответственно, вариантов его использования. Рассмотрим некоторые из них.
Пусть в регистр управления записано число. устанавливающее в 1 разряд 04 (разрешён счёт), а разряды 00...04 установлены в 0. В регистре предустановки имеется некоторая константа. После занесения команды в регистр управления число из регистра предустановки перепишется в счётчик и там начнётся вычитание из него по единице в определённом темпе - обратный счёт времени. Как только содержимое счётчика 177710 станет равно нулю, в него снова будет переписано число из 177706, снова начнётся вычитание и т.д. Задавая в 177706 различные числа, можно изменять время цикла таймера - это «тонкая регулировка» хода наших часов. Но есть и грубая - в зависимости от записанного в 177712 кода команды можно замедлять его «ход» в 4, 16 и 64 раза, для чего достаточно задать равными 1 один или оба разряда - 05 и 06 - «множители времени». Если разряд 03 равен 1. то таймер выполнит только один цикл счета, а если задать равным 1 разряд 02 и сбросить в 0 разряд 07, то после перехода счётчика через 0 окончание цикла таймера будет отмечено единицей, появившейся в разряде 07. Если же установить в 1 разряд 01, то, выполнив один цикл счета от константы предустановки до 0, счётчик продолжит счёт «вкруговую» уже без учёта константы предустановки.
При работе таймера отсчитанный промежуток времени определяется умножением разности показаний счётчика за истекшее время на те множители, в разряды которых записаны единицы, и на единичный период времени. Этот период, измеренный для нескольких БК-0010, в среднем равен 42.9 мкс и может для разных экземпляров БК варьировать в пределах долей процента (реже - нескольких процентов).
Приведём пример. Пусть мы, запуская таймер, записали в регистр 177712 код 160 (единицы в разрядах 04, 05 и 06) и получили разность показаний таймера за отсчитанный промежуток времени, равную 154432 (55578д). Вычислим: 55578 * 4 * 16 * 42.9 = 152594956.8 мкс, или примерно 152.6 с. Естественно, чтобы отсчитать такую разность, необходимо иметь в регистре 177706 число, большее 154432. Приведём таблицу, которая позволит установить максимальное время, отсчитываемое таймером в пределах одного цикла (см. табл. 1).
Как можно понять из вышеизложенного, максимальный цикл задаётся числом 177777, занесённым в регистр 177706 (вместо этого можно просто обнулить регистр 177706, тогда после первого же вычитания произойдёт заем в старшем разряде, и число станет равно 177777).
Таблица 1 Код
Множитель
Макс. время цикла, с
20
х 1д
2,812
120
х 4д
11,25
60
х 16д
45,00
160
х 64д
180,0
Таким образом, таймер позволяет отсчитывать промежутки времени до 3 мин. А если надо больше? Тогда придётся написать программу, которая могла бы фиксировать моменты перехода счётчика таймера через ноль и подсчитывать число таких переходов. Цикл таймера при этом целесообразно задать кратным какой-либо целой единице времени. Попробуем задать время цикла равным 1 мин.
Выберем константу предустановки. Ясно, что подходит лишь код 160 - все другие дают время цикла менее 1 мин. Подсчитаем константу. Известно, что 1 мин = 60 миллионов мкс. Вычислим: (60000000/64) / 42.9 = 21853 (округлённо). Переведём в восьмеричную систему: 21853д = 52535. Остаётся записать полученное число в 177706, а код 160 - в 177712, и счётчик таймера будет выполнять цикл длительностью ровно в 1 мин. Эксперименты с таймером удобно проводить в МСД, используя для непрерывного чтения регистра 177710 команду «Ц».
Для фиксации количества циклов таймера (моментов перехода счётчика через 0) можно предложить разнообразные приёмы, например, периодически опрашивать регистр 177710 (не менее двух раз в течение цикла), использовать разряд индикации регистра 177712 или повторно запускать таймер в режим однократного счета.
Для чего ещё может понадобиться таймер, кроме отсчета времени? Это идеальный генератор случайных чисел, если только обращения к нему производятся не программой, а человеком (например, по нажатии клавиши). Такой генератор будет давать не псевдослучайные, а истинно случайные числа, так как момент нажатия оператором клавиши зависит от очень многих факторов, и угадать показание таймера в этот момент невозможно. Если же к таймеру обращается программа, последовательность будет далека от случайной, так как и таймер, и процессор используют одну и ту же тактовую частоту, и поэтому процессы в них коррелируют. (Генерация случайных чисел с помощью таймера использована в известной игре для БК STREAP SHOW. - Прим. ред.)
В заключение несколько замечаний. Системный таймер, будучи запущенным, работает автономно от процессора, поэтому его счётчик отсчитывает время независимо от того, стоит процессор или работает, есть в БК работающая программа или нет. Таймер одинаково успешно работает в Бейсике и Фокале, с программами в кодах, запущенными из МСД и монитора, и даже в тот момент, когда БК-0010 работает с магнитофоном. Но опрос регистра 177710 (если для подсчёта числа циклов таймера используется этот способ) необходимо проводить не реже, чем два раза за цикл таймера, иначе можно пропустить переход через 0 и показания «часов» исказятся. Таймер, в свою очередь. «стоит» ли он или «идёт», никак не влияет на работу программы пользователя, в том числе и на скорость её исполнения. И наконец, одно не совсем приятное замечание. Поскольку таймер по техническим условиям в состав БК-0010 не входит, то на некоторых экземплярах его регистры могут быть неисправны, что не служит основанием для предъявления претензий заводу-изготовителю. К счастью, эта неисправность, довольно распространённая на БК-0010 3-5 лет назад, сейчас встречается всё реже, так что можно смело использовать таймер в своих программных разработках.
При выходе в Бейсик или Фокал таймер останавливается, поэтому запускать его нужно в той среде, в которой он будет работать.
(Ещё одной сферой применения таймера является организация на БК-0010 многопрограммного режима работы. Подробно о выполнении на БК нескольких программ одновременно говорится в журнале «Информатика и образование», №2 за 1992 г. - Прим. ред.)
- Регистр порта ввода-вывода (адрес 177714). Используются все 16 разрядов, доступные по записи и чтению. Но этот регистр особенный, он как бы составлен из двух отдельных регистров для ввода и вывода. Данные записываемые по адресу 177714, передаются в регистр вывода, а читаются из регистра ввода. Прочитать данные, записанные в регистр вывода, невозможно, поэтому, если необходимо сохранить к ним доступ, нужно записать их не только в порт, но и в специально зарезервированную для этого ячейку памяти (адрес 256). Необходимо заметить, что порт вывода инвертирует подаваемый на него код, т.е. при записи в него нулей на выходе будут единицы и наоборот. То же самое можно сказать и о порте ввода: при подаче на него от внешних устройств логических единиц программно читаются нули при подаче нулей - единицы. При «привязке» к порту внешних устройств нужно это учитывать.
- Регистр системных внешних устройств (адрес
177716). Используются все
16 разрядов, но для разных целей, и поэтому
они неравнозначны.
- Разряды 0...3 предназначены для внутренних нужд ЭВМ, служат для задания режима работы процессора, доступны только по чтению.
- Разряды 4...7 предназначены для управления системными внешними устройствами ЭВМ (магнитофоном, телеграфным (ТЛГ) каналом, клавиатурой, встроенным динамиком), и на них мы остановимся подробнее. Эти четыре разряда представляют собой внутренний порт ввода-вывода и организованы совершенно так же, как только что описанный внешний порт. Это как бы два отдельных регистра ввода и вывода, поэтому запись и чтение информации по этим разрядам имеют разное значение.
Разряды регистра ввода:
- Разряд 04 служит для чтения информационного сигнала с ТЛГ-линии.
- Разряд 05 - для чтения информации с магнитофона.
- Разряд 06 - индикатор нажатия клавиши: если нажата любая клавиша (кроме «СТОП» и регистровых «НР», «ПР», «СУ», «СТР», «ЗАГЛ»), то в этом разряде 0, если ни одна из клавиш не нажата - 1. Широко используется в программах как в кодах, так и на языках высокого уровня для индикации нажатия клавиш, ввода информации «на ходу» (без останова программы), организации циклов автоповтора и т.п. В самой ЭВМ используется для организации режима «повтор».
- Разряд 07 служит для чтения сигнала готовности с ТЛГ-линии. Этот разряд, как и 04, может использоваться как по прямому назначению, так и для других целей, это дополнительный канал связи ЭВМ с внешними устройствами. Для задействования ТЛГ-ввода/вывода на плате ЭВМ необходимо установить перемычки (на БК-0010. выпускаемых некоторыми предприятиями, перемычки установлены, а на других - нет, так что этот вопрос нужно решать применительно к конкретному компьютеру).
Разряды регистра вывода:
- Разряд 04 служит для передачи информации на ТЛГ-линию, его исходное состояние - 1.
- Разряд 05 - для передачи сигнала на магнитофон (при записи) или сигнала готовности на ТЛГ-линию. поэтому одновременный обмен информацией с магнитофоном и ТЛГ- линией невозможен. Исходное состояние - 0.
- Разряд 06 - для передачи информации на магнитофон (при записи) и сигнала на пьезодинамик ЭВМ (при записи на МЛ и нажатии клавиши). Очень широко используется в программах в кодах и на Бейсике для создания звуковых эффектов если с определённой периодичностью записывать в этот разряд чередующиеся 0 и 1. мы услышим звук, воспроизводимый как встроенным пьезодинамиком, так и подключённым к выходу БК «МАГНИТОФОН» любым усилителем низкой частоты. Исходное состояние - 0.
Здесь необходимо одно пояснение: почему для выдачи информации на магнитофон используются два разряда - 05 и 06. Дело в том, что в БК-0010 для записи на магнитофон был принят двухуровневый сигнал. При записи «нуля» сигнал идёт с обоих разрядов и имеет большую амплитуду, а при записи «единицы» - только с разряда 06 и амплитуда его меньше. Это сделано для коррекции частотной характеристики тракта записи магнитофона и получения более качественной записи информации. Но в БК-0010 поздних выпусков от этого отказались, сигнал на магнитофон с разряда 05 уже не передаётся, в схему компьютера внесены соответствующие изменения, но программа обслуживания выхода на магнитофон осталась той же и по-прежнему обслуживает оба разряда. Эти изменения могут отсутствовать и в БК-0010, выпускаемых некоторыми из предприятий.
- Разряд 07 служит для дистанционного управления двигателем магнитофона: при записи в него нуля двигатель включается, а единицы - отключается. Управление двигателем производится через установленное на плате БК электромагнитное реле.
- Разряды 08...15 предназначены для задания адреса запуска системы и доступны только по чтению. Адрес запуска в БК-0010 принят равным 100000, он и задан в старшем байте регистра 177716, причём его младший байт принимается равным 0.
Контрольные вопросы и задания
- Как известными вам способами проследить за изменениями в регистре
177662 при нажатии разных клавиш? Проверьте
это экспериментально.
-
Дайте в МСД директиву 177662АЦ, затем нажимайте различные клавиши и наблюдайте на экране их коды.
-
- Записывая в регистр 177664 различные числа, проследите за изменениями положения изображения на экране. Желательно предварительно заполнить экран каким-нибудь текстом, чтобы иметь ориентиры помимо служебной строки.
- Придумайте и осуществите на своём БК проверку работоспособности
системного таймера.
-
Дайте в МСД директивы: 177706А0,,160-Ц - при этом будет циклически читаться регистр счётчика таймера. Отметьте по секундомеру время между двумя моментами перехода таймера через ноль, этот интервал должен быть с точностью до нескольких секунд равен 3 мин, в противном случае таймер на вашем БК неисправен. Приостановить вывод информации на экран в МСД можно командой СУ/@, повторный пуск - любая клавиша или ещё раз СУ/@. Обратите внимание на то, что при прерывании вывода на экран таймер продолжает работать, после возобновления вывода его показания уже другие.
-
- Можно ли прочитать информацию, занесённую в МСД по адресу
177714? Придумайте, как всё-таки это сделать.
Всё необходимое имеется в комплекте, прилагаемом к БК-0010.
-
Программно прочитать регистр вывода 177714 нельзя, но можно подать информацию с него на регистр ввода, для чего служит входящий в комплект ЭВМ «блок нагрузок». Подключив этот блок к порту ввода-вывода, можно читать из регистра 177714 информацию, записываемую по тому же адресу.
-
- Как проверить, не разбирая компьютер, подключён ли на вашей ЭВМ
разряд 05 регистра 177716
к выходу на магнитофон?
-
Если имеется электронно-лучевой осциллограф, можно посмотреть форму импульсов на выходе «МГ» БК при записи информации на магнитофон. Если разряд задействован, импульсы будут двух разных амплитуд, если нет - только одной.
-
Как работает ЭВМ
Не пугайтесь, автор не собирается рассказывать вам о триггерах, логических элементах, регистрах сдвига, счётчиках, источниках питания и прочей электронике, это не входит в его задачу. Речь пойдёт о том, как компьютер работает с точки зрения программиста, для понимания дальнейшего изложения это необходимо.
Как уже было сказано, в памяти ЭВМ хранится программа - последовательность двоичных кодов. При её запуске процессор запоминает адрес начала программы в специальном регистре, называемом счётчиком команд, и читает первое слово по этому адресу, причём счётчик команд сразу же увеличивает своё содержимое на 2. В зависимости от того, какая команда прочитана, процессор может или сразу выполнить её (если в ней заключены все необходимые данные), или заняться «добыванием» указанных в команде данных. Последние либо могут располагаться где-то в памяти (как уже говорилось, для программы и данных используется одно и то же ОЗУ), либо входят в состав самой команды, но не в первое её слово. Команды процессора БК-0010 могут иметь длину от одного до трёх слов, причём первое из них всегда код самой команды, а два последующих, если они есть, - данные (это могут быть как сами числа для выполнения над ними каких-то операций, так и указания на адреса, по которым они расположены). Если команда состоит более чем из одного слова, содержимое счётчика команд в процессе её выполнения увеличивается не на 2, а на 4 или на 6. Таким образом, счётчик команд всегда содержит адрес следующей команды программы.
После окончания исполнения очередной команды процессор опять читает слово по адресу, указанному в счётчике команд, выполняет следующую команду и т.д. Ясно, что таким образом может исполняться так называемая линейная последовательность, когда команды следуют строго одна за другой. Но из теории программирования известно, что линейная последовательность не является достаточной для решения любых задач, необходимы ещё по крайней мере две структуры - условие и цикл. Тот, кто занимался программированием хотя бы на одном из языков, знает, что исполнение этих структур связано с изменением естественной последовательности команд. В ЭВМ это решается очень просто - достаточно прибавить к счётчику команд какое-либо число (положительное или отрицательное), и процессор послушно начнёт выполнять команды уже в другом месте программы, в соответствии с адресом, указанным счётчиком.
Как же «устроена» любая машинная команда? Не вникая в подробности, можно сказать, что в её 16 разрядах (если команда состоит из одного слова) есть код самой команды и указание на так называемые операнды, т.е. числа, с которыми нужно иметь дело. Если эти числа входят в состав команды, то, как уже говорилось, они содержатся в её втором или третьем слове. Кроме того, команда содержит ещё и указание на то, нужно ли оперировать со словом или с байтом. Все эти сведения располагаются в специально отведённых для этого разрядах первого слова команды, так называемых полях. Этим кратким описанием мы пока и ограничимся.
А что представляют собой числа, с которыми оперирует ЭВМ?
Дополнительный код
ЭВМ, как легко догадаться, оперирует с двоичными числами. Положительные двоично-восьмеричные числа от 0 до 7 мы уже рассматривали. Но это ещё не всё. Для полноценной арифметики нужны и отрицательные числа, а они изображаются в ЭВМ не совсем обычным образом. Чтобы понять, как это делается, произведём несколько действий с двоичными 4-разрядными числами. Сначала сложение:
0001 |
|
1 |
+ 0011 |
|
+ 3 |
0100 |
|
4 |
А теперь - вычитание:
0001 |
|
1 |
- 0011 |
|
- 3 |
1110 |
|
- 2 |
Но почему у нас получилось 1110, и почему это -2? При вычитании нам потребовалось сделать «заём» в старших разрядах уменьшаемого, а поскольку «цена» каждого разряда двоичного числа равна двойке, то у нас и получилось то, что получилось. При этом мы считали, что число у нас как бы «неограниченной» разрядности, и мы «заняли» единицу в следующем, пятом разряде, который не изображён.
Теперь - внимание! Старший разряд двоичного числа со знаком (в данном случае - четвёртый) называется знаковым. Если число положительное, он равен 0. а если отрицательное - 1. Значит, раз у нас число 1110 и старший разряд 1, то число это - отрицательное. А как понимать остальные 3 разряда - 110? Почему это 2? Вот это и есть дополнительный код. Какой в нём смысл? Попробуем выполнить ещё два действия:
1110 |
|
-2 |
|
0101 |
|
5 |
+ 1110 |
|
+ -2 |
|
+ 1110 |
|
+ -2 |
1100 |
|
-4 |
|
0011 |
|
3 |
А правда ли, что мы получили в первом случае -4? Проверим:
0000 |
|
0 |
- 0100 |
|
- 4 |
1100 |
|
-4 |
Результат очевиден - 1100 это действительно - 4. Так вот в чем смысл дополнительного кода: мы можем не задумываться о знаках при операциях с положительными или отрицательными числами, они получаются автоматически! Очевидно, что во втором сложении результат тоже верен, 0011 - это 3. Теперь вам ясно, как получается дополнительный код? Чтобы получить отрицательное число в дополнительном коде, нужно вычесть соответствующее по абсолютной величине положительное число из нуля. Потому код и называется дополнительным, что отрицательное и положительное числа, равные по абсолютной величине, дополняют друг друга до нуля, или, как говорят, до переноса в разряд за пределы числа. Есть простое правило получения отрицательного числа, которое позволяет делать перевод в дополнительный код в уме. Если вам нужно получить отрицательное число, возьмите положительное, равное ему по абсолютной величине, замените в нем все единицы нулями, а нули - единицами и прибавьте единицу. Попробуем. Берём 4: 0100. Заменяем: 1011. Прибавляем единицу: 1011+0001=1100. Получилось -4 - правило работает. Теперь очень легко получить любое отрицательное число в дополнительном коде. Например, возьмём число 01101100 (154). Заменим нули и единицы (это называется поразрядным инвертированием):
10010011. Прибавим единицу: 10010100. Это минус 154. Можете проверить:
00000000 |
|
000 |
- 01101100 |
|
- 154 |
10010100 |
|
-154 |
Если же нам нужно перевести число из дополнительного кода обратно, делаем «всё наоборот»: вычитаем единицу и поразрядно инвертируем полученное двоичное число. Проверьте сами, и получите абсолютную величину отрицательного числа И так - для чисел любой разрядности. А теперь попробуем выполнить ещё одно действие:
0110 |
|
6 |
+ 0011 |
|
+ 3 |
1001 |
|
-7 |
Вот это фокус! Дополнительный код почему-то «отказал»! А дело в том, что произошло так называемое переполнение, перенос единицы в знаковый разряд. То есть просто-напросто заданная разрядность оказалась мала для сложения двух предложенных чисел. Всё станет на место, если мы добавим ещё один разряд слева (и будем теперь его считать знаковым):
00110 |
|
6 |
+ 00011 |
|
+ 3 |
01001 |
|
9 |
Но наша ЭВМ оперирует не любыми числами, а только 8- и 16-разрядными, т.е. байтами и словами. Поэтому и знаковый разряд здесь фиксирован - это бит 07 для байта и 15 для слова. Как же быть при сложении на ЭВМ, если у нас фиксированный формат числа и добавить разряд мы не можем? Это предусмотрено - компьютер отмечает переполнение и даёт о нем знать специальным «флагом» результата, о котором мы поговорим чуть позже. Обнаружив, что этот «флаг» установлен, мы видим, что результат в дополнительном коде неверен и нуждается в коррекции.
Есть в ЭВМ и ещё один формат чисел - так называемые числа без знака. При этом мы включаем знаковый разряд в состав самого числа, которое в этом случае может быть только положительным. Ясно, что при этом число 1001 - это 9. Зачем ещё и такое представление чисел? Очень просто - число при этом может быть больше на один разряд. Если для выражения числа 9 в дополнительном коде нам потребовалось 5 разрядов, то для числа без знака - только 4. Если в один байт можно записать числа в дополнительном коде от -128 до +127, то без знака - от 0 до +255. Д нередко знак числа нас вовсе не интересует, поэтому не нужен и дополнительный код. Например, если надо сравнить коды символов, то при чем тут знак?
Иное дело - арифметика. Тут ради знака приходится жертвовать разрядностью числе. Но самое главное, что обычный двоичный код и код дополнительный - это, по сути, одно и то же, и мы в принципе можем оперировать числами независимо от того, в дополнительном коде они или нет. А если нам вдруг нужен знак числа, посмотрим на его старший разряд - и всё.
Попробуйте поупражняться в сложении и вычитании двоичных чисел со знаком и без него, и вы поймёте, что правила всё время одни и те же. И ещё вы заметите такую деталь: вместо того чтобы вычесть число из другого, можно прибавить к первому числу второе в дополнительном коде, ведь это есть отрицательное число! Вот оно, главное преимущество дополнительного кода, вот зачем он введён на ЭВМ: мы можем не иметь отдельного устройства - «вычитателя», достаточно только сумматора и перевода вычитаемого в дополнительный код. Это намного упрощает устройство процессора и увеличивает скорость вычислений.
А может ли ЭВМ так же просто умножать и делить? Да. Попробуем «сдвинуть» какое- либо число влево:
00010 сдвигаем влево: 00100; ещё раз: 01000
У нас было число 2. получили число 4, а затем 8. Но ведь это умножение на 2! Да, так называемый арифметический сдвиг влево есть умножение на 2. При этом мы считаем, что на место младших разрядов числа пишется 0. Совершенно очевидно, что, сдвигая такое число вправо, по тому же правилу мы получим деление на 2. А если число со знаком минус? Попробуем его умножить на 2 тем же способом:
10011 (-13д) сдвинем влево: 00110
Получили... +6! Разве это правильно? Мы опять столкнулись с тем же явлением - не хватает разрядности. Но на этот раз произошло не переполнение, а перенос, т.е. выход за пределы числа. В ЭВМ на этот случай тоже есть «флаг», который отметит, что результат ошибочный и нуждается в коррекции. Ну а как с делением чисел со знаком на 2? Тут всё в порядке, но... правило сдвига вправо было пока неполным: сдвигая число вправо, мы должны не вписывать слева нули, а расширять знаковый разряд, т.е. переписывать его значение в соседний справа бит:
10011 (-13д) сдвинем вправо: 11001 (-7)
Всё верно, ведь результат округлённый! Причём заметим, что округление происходит из-за отбрасывания младшего разряда. Но что. если нам нельзя округлять число? Ничего страшного, ведь у нас снова произошёл перенос за пределы слова и «флаг» переноса отметит, что результат нуждается в коррекции.
Итак, мы в общих чертах разобрались, как ЭВМ выполняет арифметические действия. Нетрудно теперь придумать, как можно умножить или разделить на число, не кратное двум (вспомните, что умножение - это не что иное, как многократное сложение, деление же - многократное вычитание, а также вспомните, как умножают «в столбик» и делят «уголком»). Не будем разбирать соответствующие примеры, так как это заняло бы слишком много места, а пойдём дальше в изучении архитектуры нашей ЭВМ.
Контрольные вопросы и задания:
- Как умножить двоичное число на 128?
-
Нужно выполнить 7 арифметических сдвигов влево.
-
- Придумайте правило, которое позволило бы умножать двоичные числа
на 3, и сформулируйте его словами.
-
Чтобы умножить двоичное число на 3, нужно выполнить арифметический сдвиг влево и прибавить исходное число.
-
- Пусть нам нужно сделать вычисления по формуле:
A/8+B/4+C/2=D.
При предварительном делении во всех числах может получиться округление
за счёт потери младших разрядов и точность результата снизится. Как
можно свести потери точности к минимуму?
-
Надо привести все дроби к общему знаменателю, получим: (A+2*B+4*C)/8=D. При таком порядке вычислений деление будет выполняться только один раз и возможная потеря точности от округления будет минимальной. Но в конкретной программе необходимо, конечно, учитывать возможность переполнения при последовательных умножениях.
-
Центральный процессор
Теперь пришло время более подробно рассмотреть главную часть ЭВМ - центральный процессор (ЦП). Как уже говорилось, ЦП - это универсальное логическое устройство для операций с числами. Но это не всё. Процессор имеет свою собственную память, так называемые регистры общего назначения (РОН). Этих регистров 8, и они, как и ячейки памяти и системные регистры, имеют по 16 разрядов. В них также могут быть записаны или оттуда прочитаны числа. Но в остальном регистры ЦП существенно отличаются от ячеек памяти. Прежде всего, они не входят в общее адресное пространство ЭВМ и обращения к ним особые, с применением специальных команд. Затем операции с числами в регистрах ЦП выполняются намного быстрее, чем в памяти. И наконец, целый ряд команд ЭВМ может быть выполнен только с использованием регистров, и никак иначе. Таким образом, это как бы отдельная специфическая память ЭВМ - «сверхоперативная память», имеющая большие преимущества перед ОЗУ. Жаль только, что этих регистров так мало.
Название «регистры общего назначения» не совсем точно. Действительно, шесть из них совершенно равноправны и вполне могут быть так названы. Они применяются для любых целей, их «имена» - R0, R1, R2, R3, R4, R5. Но два других регистра - специальные, их использование наравне с остальными крайне ограничено или исключено вовсе. Что это за регистры?
R6, или (как его обозначают на языке ассемблера) SP - Stack Pointer, указатель стека. Мы уже говорили, что стек - это специально выделенная область памяти. R6, или SP, всегда содержит адрес её начала, или. как говорят, вершины стека. Пользуясь этим регистром, удобно записывать в стек или извлекать из него информацию, причём адрес вершины при этом автоматически может меняться, всё время указывая на ту ячейку памяти, с которой в данный момент мы работаем. Но главное назначение стека - не столько обслуживать нужды программиста, хотя и это немаловажно, сколько временно сохранять данные, необходимые самой ЭВМ, например, при переходе к подпрограмме или при обработке прерывания.
R7, или (на языке ассемблера) PC - Program Counter, программный счётчик, или счётчик команд. Мы уже говорили, что он всегда указывает адрес очередной команды и тем самым позволяет процессору исполнять программу в правильной последовательности, а меняя его содержимое, можно изменять естественный порядок исполнения команд программы. По сути, смысл его тот же, что и у SP, - он указывает определённый адрес памяти, но в отличие от стека это не память данных, а память программ.
Есть в нашей ЭВМ и ещё один регистр, уже совсем специфический, он носит сокращённое название PS, от слов Processor Status (или PSW, PS Word). Иногда его называют также словом состояния процессора (ССП). Важность этого регистра трудно переоценить - только с его помощью ЭВМ может выполнять, например, такие необходимые действия, как проверка условий и условные переходы, а также делать многое другое, влияющее на вычислительный процесс. Поэтому рассмотрим его подробнее.
В БК-0010 используется только младший байт PS. Все его разряды доступны по записи и чтению, хотя запись в некоторые из них возможна только с помощью специальных команд или приёмов, а чтение может происходить вовсе не обязательно в том виде, к которому мы привыкли: ЦП сам, с помощью специальных команд читает эти разряды и использует их.
Что же это за разряды? Вначале идут четыре «флага», или разряда условий. Они принимают то или иное значение в зависимости от результата исполнения процессором очередной команды. Исходное состояние этих разрядов, если указанные ниже условия не выполняются, - нули:
- (разряд С) - перенос. Устанавливается в 1, если в результате исполнения команды возник перенос за пределы слова или байта. Тут. очевидно, надо оговориться, что возможен перенос только единицы ноль на результат не влияет. Правильнее было бы сказать, что С-разряд принимает значение разряда переноса;
- (разряд V) - переполнение. Устанавливается в 1. если в результате исполнения команды имело место переполнение, т.е. перенос в знаковый разряд слова или байта;
- (разряд Z) - ноль. Устанавливается в 1, если результатом исполнения команды является нулевое содержимое слова или байта;
- (разряд N) - отрицательность. Устанавливается в 1. если результатом исполнения команды является отрицательное число в слове или в байте.
Используя содержимое этих разрядов, можно организовать так называемое ветвление программы, т.е. переходы по условию, или что то же самое, по результатам исполнения команды. Помимо того, что эти разряды устанавливаются в 0 или 1 автоматически по результатам исполнения каждой команды, существуют и специальные операторы для их «ручной» установки.
Следующий разряд - 04, или T-разряд. Он имеет особое назначение. Если содержимое T-разряда равно 1, то программа приостанавливается каждый раз после выполнения очередной команды (происходит так называемое прерывание по T-разряду). Этот режим процессора широко используется в специальных программах-отладчиках, позволяющих исполнять программы по одной команде с целью их отладки (выполнять трассировку). Просто так записать в T-разряд единицу нельзя, для этого существует специальный приём, с которым мы познакомимся позже.
И наконец, последние три разряда (05, 06, 07) - это так называемые разряды приоритета процессора. Что это такое? В процессе работы ЭВМ ЦП постоянно обрабатывает информацию, или, проще говоря, решает задачи. Но ЭВМ работает и с внешними устройствами, например с клавиатурой. Можно организовать работу с клавиатурой двояко. По первому способу ЦП после выполнения определённого числа команд прерывает решение задачи и обращается к клавиатуре, опрашивая её. Если ни одна клавиша не нажата, вычисления будут продолжены, затем снова следует опрос клавиатуры и т.д. Этот способ довольно нерационален - независимо от того, нажата ли клавиша. ЦП вынужден всё время «отвлекаться» и тратить время на опрос клавиатуры. А если учесть, что на нажатие клавиши ЭВМ должна реагировать мгновенно (чтобы не создавать неудобств в работе), то понятно, что все клавиши должны опрашиваться никак не реже, чем 100 или даже 1000 раз в секунду.
Второй способ куда лучше - пусть ЦП решает задачу, а если будет нажата клавиша, то он получит так называемый запрос на прерывание. Это специальный сигнал, сообщающий процессору, что с какого-то из внешних устройств поступила порция информации и оно ждёт, чтобы ЦП отреагировал. ЦП прервёт решение задачи, обработает прерывание и продолжит вычисления.
Но постойте! Есть ведь такие задачи, когда ЦП никак не может «отвлечься», например работа с магнитофоном. Лента ведь не будет стоять и ждать, пока ЦП обработает прерывание... Вот для этого и служит приоритет. Если приоритет, установленный на данный момент для ЦП (как говорят, приоритет текущей задачи), ниже, чем установленный приоритет запроса на прерывание, то ЦП прервёт работу и обработает прерывание. Если же приоритет текущей задачи равен или выше приоритета запроса, то ЦП сначала закончит вычисления, а уже потом обработает прерывание. Сразу поясним, что приоритет запросов на прерывание от внешних устройств (у БК-0010 их, по сути, может быть всего два - клавиатура и внешний таймер) установлен для данной конфигурации ЭВМ раз и навсегда А вот приоритет ЦП может меняться и определяется содержимым указанных трёх разрядов PS.
Источник прерывания |
Приоритет |
Адрес вектора |
Использование источника |
---|---|---|---|
Зависание |
1 |
4 |
|
Ошибочный машинный код |
2 |
10 |
|
Т-бит |
3 |
14 |
|
Сбой питания |
4 |
24 |
|
Радиальное стат. (клавиша «СТОП») |
5 |
4 |
Прерывание программы пользователя с пульта оператора |
Радиальное дин. |
7 |
100 |
Прерывание по требованию устройства |
На самом деле, в колонке приоритет тут и в ремонтной документации БК приведены порядковые номера прерываний, а каков приоритет на самом деле, остаётся загадкой до сих пор.
Сколько может быть задано различных уровней приоритета процессора? Очевидно, восемь, ведь в три двоичных разряда можно записать восемь различных комбинаций. Минимальный приоритет, когда прервать работу процессора может любое устройство, - 000, максимальный, когда работа процессора непрерываема ничем, кроме клавиши «СТОП» (это - «внеприоритетное» внешнее устройство), - 111.
Для справки приведём таблицу, содержащую сведения о прерываниях, их приоритетах и использовании. Эта таблица взята из внутризаводской «ремонтной документации» по БК-0010 - см. табл. 2.
Не удивляйтесь, что прерывания по зависанию и по клавише «СТОП» имеют один и тот же вектор, но разные приоритеты. «Ремонтная документация» объясняет, в чём дело. При нажатии на клавишу «СТОП» возникает радиальное прерывание IRQ1. Микропрограммная обработка этого прерывания содержит обращение по записи к регистру с адресом @#177676. Однако в данной модификации микро-ЭВМ ячейка с таким адресом отсутствует, поэтому происходит обработка прерывания по зависанию процессора. Попутно отметим, что кроме зависания и клавиши «СТОП» прерывание по вектору 4 обслуживает также и отработку команды процессора HALT.
Кстати говоря, хотя вектор прерывания по «СТОП» и по зависанию один и тот же, существует простой способ программного определения причины прерывания. Для этого служит разряд 02 регистра @#177716, выставляемый в 1 при нажатии на клавишу «СТОП» (или при отработке HALT):
PRST: ... ; п/пр обработки прерывания ... ; по вектору @#4 BIT #4,@#177716 ; проверка разряда 02 BEQ ZAVIS ; на обработку зависания ... ; обработка нажатия ... ; на клавишу «СТОП»
Этот способ не описан нигде, кроме всё той же «ремонтной документации». - Прим. ред.
Векторы прерываний
Мы уже не раз упоминали термин «прерывание». Пора, наконец, разобраться, что же это такое. Для начала попробуем дать общее определение. прерывание - это приостановка выполнения процессором решения текущей задачи и переход к обслуживанию внешних устройств или программных запросов. Как происходит так называемая обработка прерывания? Вначале рассмотрим обработку прерываний от внешних устройств.
Пусть ЦП выполняет операции по решению текущей задачи, а в это время поступает запрос на прерывание от внешнего устройства. Этот запрос обычно подаётся по одной из отдельных электрических цепей ЦП, следовательно, он не является командой в обычном понимании. Если приоритет ЦП, установленный для текущей задачи, меньше, чем приоритет внешнего устройства, от которого пришёл запрос, происходит прерывание. При этом процессор завершает выполнение очередной команды и прекращает ввод следующих команд, после чего он записывает в стек текущее ССП (содержимое PS). а затем содержимое PC, т.е. не что иное, как адрес следующей команды. Эти данные необходимо сохранить, ведь после окончания обработки прерывания процессор должен продолжить решение текущей задачи начиная со следующей команды и с тем же самым приоритетом. Часто требуется сохранить не только содержимое PC и PS, но и другие данные, тогда об этом должен позаботиться уже программист. Записав ССП и PC в стек. ЦП выполняет так называемый переход по вектору прерывания.
Вектор прерывания - это просто два машинных слова, записанных одно за другим в специально выделенной зоне ОЗУ - системной области. Первое из этих слов представляет собой адрес начала программы обработки данного прерывания, второе - ССП, установленное для него. Из этого описания совершенно ясно, что каждое прерывание должно иметь свой собственный вектор. Итак, процессор, выполняя переход по вектору прерывания, переписывает его первое слово в PC, а второе - в PS. После чего, естественно, следующей выполняется команда, записанная в памяти по адресу, указанному в PC, т.е. начинается выполнение программы обработки прерывания. Приоритет ЦП при этом будет установлен согласно новому содержимому PS, которое, как мы помним, переписано из второго слова вектора. Если этот приоритет достаточно низкий, т.е. допускает на фоне обрабатываемого новое прерывание, то оно может быть также обработано и т.д. Те, кто занимался практическим программированием, не могут не заметить в этом процессе некоей аналогии с обращением к подпрограмме. Правда, обработка прерывания несколько осложняется изменениями ССП, а следовательно, приоритета и битов («флагов») условий. Но раз уж мы усмотрели эту аналогию, то вспомним, что из любой подпрограммы должен быть возврат к выполнению основной программы. Точно так же существует и возврат из прерывания, для чего в конце программы его обработки должна быть записана специальная команда. Обнаружив её, ЦП выполняет действия, обратные только что описанным, - восстанавливает из стека содержимое PC и PS и, естественно, продолжает выполнение прерванной программы, ведь теперь в PC - адрес её следующей команды. Отметим ещё раз, что при этом приоритет процессора (а что ещё важнее, «флаги» условий CVZN и, конечно, T-разряд) также восстанавливается.
Помимо прерываний от внешних устройств существуют и так называемые командные прерывания, или программные запросы. Их обработка отличается от только что описанного процесса лишь тем. что прерывание вызывается особой командой, встретившейся по ходу программы. Ясно, что программный запрос на прерывание удовлетворяется всегда и не зависит от приоритета - не может же процессор игнорировать команду, которую сам прочитал! Командное прерывание, таким образом, ещё больше похоже на переход к подпрограмме.
Векторы прерывания располагаются, как было указано выше, в специально отведённой зоне ОЗУ. В БК-0010 эта зона занимает адреса 0...276. Вычисления показывают, что в данной зоне можно разместить до 48 векторов прерывания. Но, разумеется, такого количества векторов в БК нет, а свободные ячейки памяти используются для других целей.
Откуда же берутся в системной области ОЗУ векторы прерывания? Очень просто: они переписываются туда из ПЗУ при запуске системы. А ЦП уже «знает», по какому адресу записан каждый вектор, эти сведения заложены в него аппаратно, и при поступлении соответствующего запроса он ими пользуется.
Перечислим кратко имеющиеся в БК-0010 векторы прерывания. Вектор прерывания обычно получает «имя» от того адреса, по которому записано его первое слово. Эти адреса строго фиксированы для данного типа процессора и поэтому точно определяют вектор.
- Вектор 4 - прерывание по зависанию. Переход по этому вектору выполняется как по запросу внешних устройств, так и программно. «Внешним устройством» здесь является клавиша «СТОП», а программный переход по вектору 4 вызывает команда HALT - «останов» (код этой команды - 0). Кроме того, прерывание по этому вектору может вызвать и сам ЦП при зависании (что это такое, уже описано выше), откуда и следует его название. Клавиша «СТОП», в отличие от других внешних устройств, вызывает прерывание всегда (понятие приоритета на неё не распространяется).
- Вектор 10 - прерывание по резервному коду. Как уже было сказано, не все возможные коды использованы в наборе команд ЦП. Если в процессе выполнения программы встретится «неизвестный» код, происходит прерывание по вектору 10.
- Вектор 14 - прерывание по T-разряду. Прерывание по данному вектору происходит после выполнения очередной команды, если T-разряд ССП установлен равным 1 .
- Вектор 20 - прерывание по команде IOT. Иногда используется в операционных системах.
- Вектор 24 - прерывание по аварии сетевого питания. В БК данный вектор не используется, хотя программа обработки в ПЗУ имеется. Вектор просто не задействован аппаратно - соответствующий вход процессора (ACLO) не подключён к устройству, вызывающему прерывание при снижении напряжения питания. Это устройство в составе БК попросту отсутствует.
- Вектор 30 - командное прерывание EMT. Этот очень широко используемый вид прерывания, как и следующий (по вектору 34), будет подробно описан в дальнейшем.
- Вектор 34 - командное прерывание TRAP.
- Вектор 60 - прерывание от клавиатуры. Запрос на прерывание по этому вектору поступает при нажатии любой клавиши (кроме «СТОП» и регистровых). Запрос удовлетворяется, если приоритет процессора менее 4. Если приоритет равен или больше 4, запрос на прерывание «зависает» до окончания обработки процессором текущей задачи или до снижения приоритета, в этом случае говорят, что прерывание от клавиатуры запрещено.
- Вектор 100 - прерывание по таймеру. Помимо внутреннего (системного) таймера, описанного выше, к БК-0010 можно подключить и внешний, или таймер по прерываниям. Его преимущество в том, что ЭВМ, работая с ним, не должна обязательно периодически опрашивать регистр счётчика, а таймер сам, например один раз в секунду, вызывает прерывание. Приоритет таймера такой же, как у клавиатуры, и прерывание по вектору 100 разрешено, если приоритет процессора ниже 4. Программа обработки данного прерывания предусмотрена в Фокале, содержимое счётчика таймера вызывается функцией FCLK(), которая, кстати, не описана в руководстве по Фокалу.
(Другое название этого прерывания - «Прерывание пользователя», так как фактически оно может быть вызвано любым пользовательским внешним устройством, подающим сигнал на контакт В1 разъёма порта УВВ (справа) или А5 разъёма системной шины (слева). Таким образом, при описанном выше использовании прерывания таймер является примером внешнего устройства. Простейший способ вызова прерывания по вектору 100 - подача на контакт В1 порта УВВ сигнала «земля» (или «общий») с контактов А11, В11. А18, В18, А19 или В19 (так как порт УВВ работает с инверсными сигналами). Наиболее часто данное прерывание используется для печати копии экрана на принтер при нажатии замыкающей указанные контакты кнопки, как. например, в программе GRAFIX фирмы ALTEC или резидентном драйвере SCREW ANIRAM-MIRIADA. - Прим. ред.)
- Вектор 274 - прерывание от клавиатуры по нижнему регистру. Как уже было сказано, в регистре данных клавиатуры (адрес 177662) при нажатии клавиши формируется 7-разрядный код. Его преобразование в код нижнего регистра осуществляется благодаря тому, что для обработки нажатия таких клавиш предусмотрен отдельный вектор прерывания. В остальном вектор 274 не имеет отличий от вектора 60, его приоритет тот же самый.
Остальные ячейки в области векторов прерывания БК-0010 используются для других целей. Но нам вполне достаточно и имеющихся векторов. Позже мы рассмотрим подробнее, что и как можно сделать с их помощью, а сейчас перейдём ко второй теме нашего разговора - к языку ассемблера.
Контрольные вопросы и задания
- Сколько регистров имеется в составе ЦП? Каковы их названия?
-
Восемь регистров общего назначения: R0, R1, R2, R3, R4, R5, R6 (или SP) и R7 (или PC), а также специальный регистр PS.
-
- Какие «флаги» состояния вы знаете, их буквенные
обозначения и краткие наименования?
-
С - перенос, V - переполнение, Z - ноль, N - отрицательность.
-
- Если в результате выполнения какой-либо команды произошло переполнение
и число стало отрицательным, то какое значение примут разряды условий?
-
С=0; V=l; Z=0; N=1.
-
- Что обеспечивает T-разряд ССП при его
установке в единицу? Зачем это нужно?
-
Прерывание программы после выполнения каждой команды, это нужно для обеспечения отладочного режима (трассировки).
-
- Что хранится в первом слове вектора прерывания? А во втором?
-
Адрес программы обработки прерывания. ССП для данного прерывания.
-
- Чем отличаются командные прерывания от прерываний, вызываемых внешними
устройствами?
-
Командное прерывание вызывается особой командой программы, понятие приоритета на него не распространяется.
-
- Учитывая, что разряды приоритета в ССП имеют номера
05, 06 и
07, напишите, какое машинное слово надо записать
в регистр PS, чтобы получить приоритет
0, приоритет 4,
максимальный приоритет. Считайте при этом, что остальные разряды
PS равны нулю. Для облегчения задачи «нарисуйте»
и пронумеруйте разряды PS, а получившееся
потом число переведите в восьмеричное.
-
Приоритет 0 - 000000, приоритет 4 - 000200, максимальный приоритет (7) - 000340.
-
- Какой приоритет имеет клавиша «СТОП» БК-0010?
-
Это внеприоритетное внешнее устройство.
-
Языки программирования.
Что такое ассемблер
Вы, конечно, знакомы с имеющимися на БК-0010 так называемыми языками высокого уровня. Это Фокал и Бейсик-MSX, которые находятся в ПЗУ (такое программное обеспечение, зашитое в ПЗУ. называется резидентным). В составе ПО БК-0010 имеются и другие языки программирования, но, в отличие от резидентных, они загружаются в ОЗУ. Все эти языки (вильнюсские Бейсик-85 и Бейсик-87, Т-язык, Форт-83 и т.п.) имеют общую черту - это языки-интерпретаторы. Что это значит?
Программа на языке интерпретирующего типа пишется в виде текста (листинга), в котором с помощью условных символов, цифр и слов указывается последовательность действий ЭВМ. (Отметим, что под термином «листинг» понимается иногда вообще произвольный список или просто распечатка любых данных. Мы же будем для краткости при менять этот термин в смысле «текст программы в условных обозначениях языка программирования»). Но нам известно, что единственный язык, доступный ЭВМ, - это язык машинных команд, двоично-восьмеричный код. Написанная на языке высокого уровня программа может быть исполнена не непосредственно, а лишь с помощью программы- интерпретатора (входящей в состав языка), причём листинг построчно переводится интерпретатором в последовательность машинных команд, которую и исполняет ЭВМ. Программа хранится в памяти ЭВМ практически в том виде, в котором написана (небольшая модификация листинга, происходящая при этом, принципиального значения не имеет).
Если нужно ещё раз исполнить программу, интерпретатор снова переводит её в машинные команды, причём не всю сразу, а по частям (обычно по одному оператору). Преимущества интерпретатора - простота языка и удобство программирования (программу можно просмотреть в той же форме, в которой она написана, исправить, дополнить и т.п.), а также компактность представления программ в ОЗУ в виде листинга. Недостатки - низкая скорость исполнения программ (ведь интерпретатор выполняет перевод в машинные коды каждый раз заново) и то, что программа-интерпретатор всегда должна присутствовать в памяти. (Прошитый в ПЗУ БК-0010.01 вильнюсский Бейсик-MSX является своего рода промежуточным звеном между интерпретаторами и компиляторами. Как и последние. он непосредственно перед выполнением программы транслирует её в последовательность «машинных псевдокоманд» (шитых кодов). Однако, как и в интерпретаторе, отсутствует возможность получения автономной машинной программы и при исполнении в памяти одновременно должен присутствовать листинг, сгенерированный шитый код и сам транслятор Бейсика (в ПЗУ). Следует тем не менее заметить, что с помощью определённых хитростей всё же удаётся получать «автономные» программы, не содержащие листинга, но для их работы всё равно необходимо наличие ПЗУ с Бейсиком. - Прим. ред.)
Помимо языков интерпретирующего типа есть и так называемые транслирующие языки, или языки-компиляторы (термины «компиляция» и «трансляция» в данном случае означают одно и то же). Обычно это серьёзные языки программирования для мощных компьютеров - Фортран, Паскаль, Кобол, Си, ПЛ/1, Ада и др. Программа на таком языке пишется также в виде листинга, а затем транслируется, т.е. переводится в машинные команды, но при этом не исполняется. При таком переводе, например, на Фортране вместо одного оператора могут быть сгенерированы десятки и даже сотни машинных команд. В результате трансляции образуется программа в машинных кодах (так называемый загрузочный модуль). Поскольку это уже не листинг, а готовый машинный код, программа исполняется несравненно быстрее, чем на языке-интерпретаторе, а дальнейшее присутствие транслятора в памяти становится излишним. К недостаткам языков этого типа относится прежде всего то, что программа-транслятор, входящая в состав языка, при переводе листинга на язык машинных команд строит загрузочный модуль не всегда оптимально. В зависимости от конкретного типа транслятора. вида языка и реализуемого алгоритма эта «неоптимальность» может приводить к увеличению длины программ в 1.5 - 3 раза и соответствующему снижению возможного быстродействия ЭВМ. Происходит это потому, что язык высокого уровня по структуре весьма сложен, и при трансляции листинга всегда присутствует некоторая неоднозначность, когда можно сделать перевод «и так, и эдак»
(Более серьёзной причиной резкого увеличения объёма сгенерированной программы является то, что к ней пристыковывается так называемый библиотечный модуль, содержащий все подпрограммы реализации процедур и функций, относящихся к какому-либо классу (например, все операции ввода-вывода). Подобное «техническое решение» обеспечивает унификацию и упрощает работу компилятора, но реально большая часть пристыкованных в составе данного модуля подпрограмм в конкретной программе не используется и является своего рода «балластом». В интерпретаторе же все подпрограммы реализации, как правило, зашиты в самом интерпретирующем модуле, что, в свою очередь, порождает другие недостатки, например невозможность расширения перечня используемых функций, тогда как в компиляторе это можно сделать простым включением новых подпрограмм в состав старого библиотечного модуля. - Прим. ред.)
Кроме того, обратный перевод с машинных кодов на язык высокого уровня практически невозможен. Очень сильно на оптимальность трансляции влияет и то, что программы на языках высокого уровня должны быть переносимы на уровне листингов, т.е. должны исполняться по возможности одинаково на машинах разных систем. Получается, что стандарт языка единый, а под него должны «приспосабливаться» разные машины. ЭВМ же, как правило, свойственны жёсткая архитектура и система команд, не всегда соответствующие средствам языка. Как разрешить это противоречие и как, с одной стороны, использовать все ресурсы ЭВМ, а с другой, сделать программу оптимальной, а средства её написания - удобными для программиста?
На заре компьютерной техники программы составлялись непосредственно в виде последовательности машинных команд (в «машинном коде»), при этом они, естественно, полностью соответствовали архитектуре ЭВМ и были оптимальны как по длине, так и по быстродействию (конечно, при известной квалификации программиста). Но программирование в машинных кодах - каторжный труд, превращающий человека в придаток машины, большая часть ресурсов человеческого мозга при этом уходит не на собственно программирование, а на запоминание, вычисление и прочие операции, связанные с кодами. Вскоре программисты сообразили, что эти функции можно передать ЭВМ, освободившись тем самым от рутинного труда, а мощность машин стала достаточной, чтобы помочь в этом программисту. Так из машинного языка родился язык ассемблера. Как и язык высокого уровня, он позволяет пользоваться системой обозначений, удобной и (по сравнению с машинными кодами) легко запоминающейся, но, в отличие от других языков, это язык низкого уровня, или машинно-ориентированный язык. Язык ассемблера всегда приспособлен для машин определённого типа, рассчитан на определённую систему команд. Поэтому он позволяет использовать все, без исключения, ресурсы машины, достигать максимально возможной компактности и быстродействия программ. Каждой команде на языке ассемблера соответствует своя команда в машинных кодах, поэтому возможен и нетруден как прямой (с ассемблера в коды), так и обратный перевод. Структура языка проста, и программа- транслятор получается достаточно компактной. Все эти преимущества обусловили чрезвычайно широкое распространение языков ассемблера (на каждой ЭВМ он свой) и их высокую популярность. Тем более велика роль ассемблера на таких ЭВМ, как БК-0010 с её далеко не безграничными возможностями как по быстродействию, так и, что особенно важно, по ресурсам памяти.
Итак, что же такое ассемблер? Мы уже знаем, что это машинно-ориентированный язык, система условных обозначений для программирования в машинных кодах. Кроме того, словом «ассемблер» обозначают и специальную программу, обеспечивающую программирование на языке ассемблера, иногда при этом добавляя: «ассемблер-система». И ещё одно значение придают этому термину, когда говорят «программа на ассемблере», - под этим обычно понимается программа в машинных кодах, написанная на языке ассемблера и оттранслированная в коды.
А что же машинные коды, их уже знать не требуется? В общем, да. Ассемблер-система обеспечивает перевод листинга программы в машинный код, а другая специальная программа, называемая дизассемблером, может легко выполнить обратный перевод машинных кодов на язык ассемблера, причём, оба преобразования делаются быстро и без ошибок. (На самом деле программы-дизассемблеры не всегда работают без ошибок. Многие из этих программ, реализованных на БК, не распознают входящие в «тело» кодового модуля массивы чисел и символов, пытаясь и их интерпретировать как команды ассемблера. Это может сделать полученный листинг трудночитаемым, особенно для начинающих программистов. - Прим. ред.) И тем не менее бывают случаи, когда знание машинных кодов может оказаться весьма полезным, избавляя от необходимости применять специальные программные средства и позволяя экономить время. Но это случается относительно редко, поэтому изучение машинных команд не входит в нашу задачу, а желающие всегда могут ознакомиться с ними по имеющейся литературе. В частности, список машинных команд в кодах и их соответствий на языке ассемблера (так называемый мнемокод) приведён в «Руководстве системного программиста», прилагаемом к БК-0010. (Более подробные сведения можно найти в кн. Осетинский Л.Г., Осетинский М.Г., Писаревский А.Н. Фокал для микро- и мини-компьютеров. Л.: Машиностроение, 1988. - Прим. ред.) Мы же по ходу изложения будем обращаться к машинным кодам только в редких случаях, по возможности ограничиваясь языком ассемблера.
Ассемблер-системы
Для БК-0010 создано множество ассемблер-систем. Не останавливаясь на ранних, уже устаревших программах, отметим, что все современные ассемблер-системы имеют ряд общих черт. Все они состоят из четырёх основных частей, объединённых в одном модуле: монитор, редактор, транслятор и компоновщик.
Монитор включается при запуске ассемблер-системы и обеспечивает с помощью ряда директив диалог с пользователем, позволяя считывать тексты программ и записывать их на МЛ, транслировать и компоновать программы, а также (в наиболее совершенных системах) запускать готовый загрузочный модуль.
По особой директиве можно перейти в редактор текста ассемблер-системы. Это, по-видимому, самая важная её часть, от совершенства которой в большой степени зависит удобство работы в ассемблере. Редактор позволяет вводить с клавиатуры и редактировать тексты (листинги) программ. Мощный редактор многократно ускоряет и облегчает работу в ассемблере, и недооценивать его значение может только тот, кто по-настоящему не работал в ассемблер системе.
Транслятор запускается директивами монитора. Он транслирует (переводит в машинные команды) текст программы, подготовленный в редакторе или загруженный с МЛ, резервирует ячейки памяти под переменные. константы и массивы, вычисляет адреса строк (операторов) программы и адреса переходов, а также выявляет некоторые ошибки, допущенные программистом.
Компоновщик, также работающий под управлением монитора, позволяет так обработать текст уже оттранслированной программы, что она становится приспособленной для работы в заданных конкретных адресах ОЗУ. Он также может состыковать несколько модулей, загруженных с магнитной ленты, между собой для их совместной работы как единого целого.
Какие же в настоящее время разработаны ассемблер-системы для БК-0010? Первыми системами, имеющими все вышеперечисленные признаки, были трансляторы МИКРО С (авторы А.М. Сомов и С.В. Шмытов, Москва). Начиная с МИКРО.3С и кончая МИКРО.1103 и МИКРО.1104 (1989-1990 гг.), эти ассемблер-системы непрерывно совершенствовались, расширялась их система команд, улучшался сервис. Однако основной их недостаток - довольно слабый и примитивный редактор текста - сохранился до самой последней версии.
Заметный шаг вперёд в области создания ассемблер-систем для БК-0010 был сделан, когда к работе подключился С.А. Кумандин (г.Коломна). Начав с усовершенствования существовавшей в тот момент версии (МИКРО.8С) и снабдив её значительно более мощным редактором, что привело к появлению МИКРО.8К, он продолжал в дальнейшем работу над ассемблер-системой, всё дальше уходя от исходной версии. Результатом этого явилось создание в 1988-1989 гг. версий МИКРО.10К и МИКРО.11К, которые можно считать уже вполне самостоятельными ассемблер-системами, так далеко они ушли от МИКРО.8С. Если версия МИКРО.8К содержала ряд ошибок, то МИКРО.10К является совершенной и тщательно отлаженной программой, имеет расширенную систему команд, удобные дополнительные форматы записи псевдокоманд, а самое главное - редактор текста, который не уступает, а во многом и превосходит знаменитый редактор EDASP. Не останавливаясь подробно на достоинствах этого редактора, можно только сказать, что, по мнению автора, сегодня это вообще лучший экранный редактор текста, созданный когда-либо для БК-0010, включая и специальные редакторы, - он имеет всё необходимое, в том числе механизм макроопераций, неограниченный буфер текста и т.п.
На базе МИКРО.10К создано ещё несколько версий ассемблера - МИКРО.11К, рассчитанный специально на прошивку в ПЗУ и имеющий ещё более совершенный редактор, МИКРО.10К-РП, редактор которого может работать как с обычным экраном, так и в режиме «РП», что позволяет при необходимости иметь дело с текстами длиной до 50000 символов, МИКРО.10-01К, специально рассчитанный на работу с клавиатурой БК-0010.01 и также имеющий режим «РП». Следует отметить, что все версии МИКРО.10К (даже те, редактор в которых на работу в «РП» не рассчитан) позволяют в режиме «РП» компоновать в памяти тексты программ большой длины (до 50000), что в основном позволяет обойтись без промежуточных записей на МЛ объектных модулей при компоновке и значительно повышает оперативность и удобство работы.
К редактору ассемблер-системы МИКРО.10К в настоящее время изготовлен, прежде всего автором этой публикации, целый ряд «приставок»-утилит, ещё более расширяющих его возможности. Эти утилиты позволяют форматировать тексты и печатать их на принтерах, выполнять произвольную перекодировку, сортировку по алфавиту и т.п. Причём всё это делается непосредственно при работе в редакторе, без промежуточной записи текста на МЛ. Все ассемблер-системы МИКРО.К, в отличие от прочих, являются перемещаемыми, могут с равным успехом работать в ОЗУ и ПЗУ, а параметры их буферов (текста и загрузочного модуля) могут быть легко и произвольно изменены пользователем, например с целью использования всей памяти при работе системы в ПЗУ.
В последнее время появился ряд новых, достаточно удобных и мощных разработок ассемблер-систем - MIKRO-SW (В.С. Коренков), пакет MSW (В.В. Савин) и другие, но все они, имея перед версией МИКРО.10К-РП некоторые (не слишком существенные) преимущества, лишены ряда её достоинств, и прежде всего присущей ей простоты и «открытости», т.е. хоть чем-нибудь да связывают программиста. Поэтому начинать с них знакомство с ассемблером всё же не рекомендуется. (Кроме вышеперечисленных сейчас существует и ряд других, не менее удобных ассемблер-систем:
M18 (автор А.Г. Прудковский) и её новая версия M19, ассемблер Турбо2 (А.М. Надежин), имеющий в своём составе модуль плавающей арифметики, и наиболее мощный на сегодня ассемблер-отладчик-дизассемблер PARADISE С.В. Клименкова. - Прим. ред.)
В дальнейшем мы будем при описании ассемблера ориентироваться в основном на версию МИКРО.10К-РП, но при необходимости отмечать и особенности других версий. Подробно описывать какую-либо, а тем более все версии ассемблер-систем, созданных для БК-0010, не представляется возможным. Устанавливая конкретную программу (рекомендуется приобрести её официально, а не украсть с помощью «свободного обмена», ведь это ваш основной рабочий инструмент!), пользователь обычно получает и краткое описание с перечислением команд ассемблера, директив редактора и монитора, а также формата записи операторов и может освоить её самостоятельно. Мы же будем рассматривать общие вопросы программирования на ассемблере, относящиеся практически ко всем версиям.
Работа с ассемблером. Формат команды
Итак, загрузив и запустив ассемблер-систему МИКРО.10К с адреса 1000, войдём в редактор по команде «EN». Эта команда обеспечивает переход в начало текста, а если нам нужно вернуться на ту страницу, на которой мы были в момент выхода из редактора, подаётся команда «ED». Теперь мы можем писать текст программы, состоящий из команд ассемблера.
В каждой строке текста программы записывается одна команда (исключением являются псевдокоманды, которых в каждой строке может быть несколько, но об этом позже). Командная строка текста программы условно разделена на четыре поля, расположенных слева направо: метка, оператор, операнды и комментарий. Поля в редакторе МИКРО.10К отделяются друг от друга любым числом пробелов (код 40) или символов «ГТ» (код 11). Удобнее всего разделять поля нажатием клавиши «ГТ» (её эквивалент для клавиатуры БК-0010.01 в редакторе МИКРО.10К - СУ/Т или клавиша «СБР»), при этом одноименные поля размещаются друг под другом, программа имеет аккуратный вид и легко читается. Оканчивается каждая строка символом «ВВОД» («ВК», код 12). Рассмотрим каждое из полей командной строки.
Первое - поле метки. Метка - это условное символьное имя, присваиваемое данной строке. После трансляции каждая строка текста превращается в последовательность кодов, размещаемую начиная с определённого (текущего) адреса. Метке, стоящей в начале строки, присваивается этот адрес, который заносится в формируемую транслятором таблицу меток. Зачем нужны метки? Как вы помните, в Бейсике или Фокале каждой строке присваивается номер, пользуясь которым можно обратиться к данной строке (передать ей управление) из любой точки программы. Точно так же в ассемблере можно обратиться к данной строке (оператору), передав ему управление по имени метки. Но есть и отличия метки от номера строки.
Во-первых, поле метки не является обязательным, т.е. помечаются не все строки, а только те. к которым нам придётся обращаться по ходу исполнения программы. Во-вторых, метками могут помечаться не только исполняемые строки программы (содержащие операторы), а вообще любые адреса (ячейки памяти). Таким образом, по метке можно не только передать управление, но и просто обратиться к любому заданному адресу, например записать в помеченную ячейку памяти какое-либо значение или прочитать из неё число. Следовательно, метка - это ещё и имя переменной или константы. С другой стороны. как передать управление, так и обратиться к ячейке памяти можно и по адресу. Метки же в общем случае служат для присваивания отдельным адресам имён, что значительно облегчает процесс программирования на ассемблере.
Метка может состоять из латинских заглавных букв и цифр, причём допустимое количество символов в имени метки не более трёх (в некоторых других версиях ассемблеров - до шести). Если метка начинается с буквы, она называется обычной и обращение к ней возможно из любого места программы с помощью любого оператора, имеющего поле операндов или передающего управление (заметим, что в серьёзных ассемблер-системах на больших ЭВМ такие метки носят название глобальных, но автор считает «механический» перенос терминологии на БК-0010 неправильным). Если же метка начинается с цифры, она называется локальной и обращение к ней возможно с помощью лишь некоторых операторов, а действительна она только до ближайшей обычной метки (вперёд или назад). Имя обычной метки, как правило, должно быть для данной программы уникальным, т.е. его повторение не практикуется (при повторении имеет смысл только последняя из одноименных меток). Имена локальных меток могут по ходу программы повторяться многократно, лишь бы одноимённые локальные метки были разделены обычными (в ранних версиях ассемблеров допускалось употребление только обычных меток) Для отличия имён меток от прочих идентификаторов ассемблера после них ставится символ «:». Имена меток и операторов могут совпадать. это не приводит к ошибкам. Если в строке имеется несколько меток, то всем им присваивается один и тот же адрес. Приведём примеры.
Обычные метки:
А: АВ: MET: N9: Т75: TR4:
Локальные метки:
2: 94: 545: 7N: 4А7: 8AR:
Запись нескольких меток в одной строке:
1:LD:M1: 1:12: А:17:14:
Второе поле командной строки - поле оператора. Оно является обязательным, без него строка лишена смысла. Оператором называется собственно машинная команда, записанная на языке ассемблера. Набор операторов в ассемблере строго соответствует набору машинных команд ЭВМ. Таким образом, оператор сообщает процессору, что надо сделать. Подробно каждый из операторов будет рассмотрен в дальнейшем, пока же мы опишем только два из них, которые будем использовать в приводимых примерах:
- CLR - очистка (обнуление) операнда (заданной тем или иным способом ячейки памяти или регистра);
- MOV - пересылка содержимого первого операнда во второй (т.е. перезапись содержимого одной ячейки памяти во вторую). При этой операции содержимое первой ячейки не меняется, поэтому название «пересылка» следует признать не совсем удачным, правильнее было бы говорить «копирование».
В состав оператора входит также указание, работает ли он со словом или с байтом. Если имя оператора обычное, то это указывает на работу со словом. Для работы с байтом к имени оператора прибавляется буква «В», например: CLR - CLRB, MOV - MOVB. Имена операторов в ассемблере записываются латинскими заглавными символами и, в отличие от некоторых команд Бейсика, не допускают никаких вариаций и сокращений.
Третье поле командной строки - поле операндов. В зависимости от стоящего в строке оператора операндов может быть два, один или ни одного (т.е. поле операндов не является обязательным). Если оператор указывает процессору, что ему надо сделать, то операнды - указание на то, с чем это надо сделать, т.е. операнды играют роль переменных или констант. Если поле операндов отсутствует, это значит, что выполняются строго фиксированные для данного оператора действия (например, команда останова процессора). Способы указания операндов весьма разнообразны, они носят название способов адресации и будут подробно рассмотрены далее.
Последнее поле командной строки - поле комментария. Как известно, комментариями называются пояснения к программе, применяющиеся с целью сделать её текст более понятным для чтения. Транслятор игнорирует комментарии, они сохраняются только в листинге программы и в загрузочный модуль (программу в кодах), разумеется, не включаются, поэтому в состав комментариев могут входить любые символы. Комментарии могут следовать после оператора или операндов без всяких разделителей, транслятор распознает их просто по месту расположения в строке. Если же комментарии записаны в пустой строке или сразу после метки (это тоже возможно, тогда данная метка считается относящейся к первой последующей строке с оператором), они должны начинаться с символа «;». В некоторых других версиях ассемблеров отделение комментариев символом «;» обязательно во всех случаях. Приведём пример записи полных командных строк ассемблера и комментариев:
; Программа начальной очистки счётчиков BEG: CLR M1 ; Очистка счётчика единиц CLR М2 ; Очистка счётчика десятков CLR М3 ; Очистка счётчика сотен OST: MOVB #1,PRO ; Записать признак останова ; в ячейку признака CON: ... ; Продолжение программы MOV М1,М4 ; Запись единиц ; и т.д., дальнейший текст ; программы
Разумеется, не всё ещё в этой записи вам понятно, но это станет ясно в дальнейшем. Пока же обратите внимание на расположение полей меток, операторов и операндов и на правила записи комментариев.
★ ★ ★
Теперь вы в общих чертах знаете, как писать текст программы. А что с ним делать потом? Разумеется, в своё время об этом будет рассказано подробно. Но по ходу изложения мы будем приводить примеры, которые многие читатели захотят проверить, а может быть, изменить и посмотреть, что получится. Такие эксперименты можно только приветствовать, для освоения ассемблера (как и любого другого языка программирования) нужна практика. Но те, кто столкнулся с ассемблером впервые, наверняка встретят трудности при решении вопроса, что делать с написанным текстом программы. Придётся нам, забегая вперёд, дать самые общие и схематические рекомендации, не объясняя пока, «что для чего и зачем» Эти рекомендации строго конкретны и относятся только к ассемблер-системе МИКРО.10К и её модификациям. Если же у вас другая ассемблер-система, самостоятельно найдите в её описании эквивалентные директивы.
★ ★ ★
Итак, текст программы готов. В конце, в отдельной строке, поставьте псевдооператор END. Всё, что следует за ним, транслятор игнорирует. Теперь нужно выйти из редактора, нажав клавишу «КТ» (или СУ/Е для МИКРО.10-01К). Вы в мониторе. Если хотите, запишите текст программы на МЛ директивой «ST». Оттранслируйте листинг директивой «СО», по окончании трансляции будет выдана длина загрузочного модуля в байтах. Если транслятор обнаружит ошибку, то на запрос «Е/С?» нажмите клавишу «Е», транслятор передаст управление в редактор, и курсор покажет ошибку («ткнёт в неё носом»). Частая ошибка, которую на первый взгляд трудно распознать начинающему, - случайное употребление русского символа вместо одинакового по начертанию латинского.
Но вот текст оттранслирован. Определим, нужна ли компоновка. Если все метки (имена и адреса) транслятор выдал в обычной форме, компоновка не требуется. Если же есть метки, выданные инверсно, то она нужна. Для компоновки дайте директиву «LS». последует запрос «Addr=». Введите число 13000 и нажмите «ВВОД». Во вторично выданной таблице инверсных меток быть не должно. Если они есть, значит, в программе имеются ошибки (мы пока даём указания, касающиеся только приводимых далее примеров, при реальном программировании всё несколько сложнее). Если всё в порядке, программу в кодах (загрузочный модуль) можно записать на МЛ директивой «SA» и запустить директивой «RU». Посмотрев, как работает программа, остановите её клавишей «СТОП», ЭВМ выйдет в монитор или МСД. Теперь вы можете с помощью директив МСД просмотреть полученную программу в кодах, она расположена начиная с адреса 13000. При желании можно ещё раз запустить программу с этого адреса. Если вы захотите внести в листинг изменения, войдите в ассемблер по адресу «повторного входа» (1002G). выйдите в редактор («ED» или «EN») и модифицируйте текст, а если его нужно уничтожить и начать всё сначала, то либо запустите ассемблер с адреса 1000, либо в мониторе ассемблер-системы дайте директиву «RS». Текст с МЛ загружается директивой «LO», а если вы хотите подгрузить его к прежнему тексту в памяти, дайте директиву «LF». Остальные директивы монитора ещё долго вам не понадобятся (а если вы не освоите ассемблер всерьёз, то вообще никогда).
Вот и всё. Как вы считаете, всё это настолько сложнее Фокала или Бейсика, чтобы стоило об этом говорить? А куда как часто автору приходилось встречать людей, которые заявляли, что на ассемблере очень трудно работать, что он ужасно сложен и что освоить его может разве лишь гений... Скромность, конечно, хороша, но не тогда, когда она мешает прогрессу.
Контрольные вопросы и задания
- Каково основное отличие языка ассемблера от языков Фокал и Бейсик
БК-0010?
-
Фокал и Бейсик - языки интерпретирующего типа, а ассемблер - компилирующего.
-
- Какая ассемблер-система самая лучшая?
-
МИКРО.10К-РП или МИКРО.10-01К. Это. конечно, шутка. Лучшая ассемблер-система - это та, которая вам нравится, или даже просто та, которая у вас есть. Некоторые же утверждают, что лучшая программа - это та, которая ещё не написана.
-
- Из скольких символов может состоять метка?
-
Не более чем из трёх в МИКРО.10К и из шести - в некоторых других системах.
-
- Локальная метка может начинаться с буквы?
-
Нет, только с цифры.
-
- Какие операторы ассемблера вы уже знаете?
-
CLR, CLRB, MOV, MOVB, END.
-
- Обязательно ли комментарии должны начинаться с символа «;»?
-
В МИКРО.10К - не обязательно, если они следуют за оператором или операндами.
-
- Наберите в ассемблер-системе программу, приведённую в качестве примера
в этом разделе (комментарии можно не переписывать), и попробуйте её
оттранслировать и скомпоновать (запускать не надо). Что обращает на
себя внимание после компоновки?
-
Метки M1, М2, М3, М4, PRO после компоновки остались инверсными, так как они не определены - отсутствуют в исходном тексте.
-
- Является ли программирование на ассемблере сложной процедурой?
-
Автор утверждает, что нет, и в ваших интересах не пытаться его опровергнуть.
-
Способы адресации
Ранее мы установили, что в команде ассемблера оператор указывает, что надо сделать, а операнд - с чем это надо сделать. Как уже было сказано, поле операндов не обязательно (оно имеется не во всех командах). Но если их нет, это означает только то, что данный оператор в операндах не нуждается - они подразумеваются по умолчанию или производится обращение не к памяти ЭВМ, а к другим её устройствам.
Способы (или методы) адресации - это не что иное, как указание на ячейки памяти, с которыми должен манипулировать оператор. Таких методов в системе команд процессора БК вполне достаточно, чтобы обеспечить высокую гибкость в построении программы Многие способы адресации, кроме указания на адреса памяти, ещё и модифицируют их, т.е., по сути, выполняют функции дополнительных команд, а следовательно, значительно экономят память и повышают скорость исполнения программы. В большинстве случаев способы адресации одинаковы для всех команд, содержащих операнды (некоторые ограничения, относящиеся к отдельным операторам, будут оговорены особо).
Прежде чем перейти к изложению конкретных способов адресации, кратко напомним, какие в нашей ЭВМ вообще возможны операнды. Прежде всего, это ячейки памяти, лежащие в пределах адресного пространства ЭВМ (с адресами 0 - 177777). Адрес можно указывать с точностью до слова или до байта, но это уже скорее относится к формату записи оператора, а не операндов. Далее, это регистры общего назначения процессора (РОН): R0, R1, R2, R3, R4, R5, R6 и R7 (на языке ассемблера регистр R6 принято именовать SP, а R7 - PC). Напомним, что SP - это указатель вершины стека, а PC - счётчик команд (указатель адреса следующей команды). Подробно эти моменты рассматривались в разделах, посвящённых архитектуре ЭВМ, сейчас же важно, что все регистры, в принципе, также представляют собой ячейки памяти.
Теперь можно перейти к описанию собственно способов адресации. Приводя примеры, будем стараться использовать только те два оператора (CLR и MOV), которые нам уже знакомы. Регистр общего назначения в общем виде будем обозначать RN (это может быть любой из РОН, однако использование в качестве операндов SP и PC может иметь некоторые особенности).
- Регистровая адресация (иногда её называют ещё регистровой
прямой, в отличие от косвенной). Операндом является один из РОН.
Пример:
MOV R2,R4 ;Переслать (скопировать) ; содержимое R2 в R4 CLR R2 ; Очистить (обнулить) R2 MOV R4,SP ; Переслать содержимое R4 в SP
Это самый простой способ адресации и, очевидно, в дальнейших пояснениях он не нуждается.
- Регистровая косвенная адресация. В РОН содержится не само
число, с которым нужно работать, а его адрес, т.е. номер ячейки
памяти, в которой оно находится. Обозначение: @RN
или (RN). Значок «@» - это так
называемое «коммерческое ЭТ», название длинное и
труднопроизносимое, поэтому в программистском жаргоне его заменяет не
слишком благозвучное, но зато удобное наименование «собачка».
Обозначения @RN и (RN)
абсолютно равнозначны, но лучше всё же писать @RN,
чтобы не путать его с адресацией с инкрементом или декрементом, о которых
речь пойдёт ниже. Примеры:
- пусть в регистре R2 записано число 1000, тогда команда CLR @R2 обнулит ячейку 1000 (ту, адрес которой указан в R2);
- пусть в R2 записано число 1000, а по адресу 1000 - число 1254. Тогда MOV @R2,R4 запишет в R4 число 1254, ведь именно его адрес указан в R2.
- Автоинкрементная косвенная адресация. Помимо основного действия
(косвенного обращения к ячейке памяти) производится изменение (модификация)
адреса этого обращения (ячейки). Слово «инкремент»
означает увеличение чего-либо, в данном случае адреса ячейки памяти,
к которой мы обращаемся (содержимого регистра, служащего указателем
адреса). А «авто» - это указание на то, что данное
увеличение происходит само, не требуя отдельной команды. Обозначение:
(RN)+. То, что знак «+» стоит
после имени регистра, указывает на порядок выполнения команды:
сначала что-то делается с ячейкой, адрес которой помещён в регистр
RN, а потом содержимое регистра увеличивается.
На сколько - зависит от формы оператора: если он работает с байтом (т.е.
записан с буквой «В»), то на
1 (переход к адресу следующего байта), а если
со словом, то на 2 (переход к адресу следующего
слова). Но из этого правила есть исключение: если в качестве указателя
адреса используется регистр SP (адресуются
ячейки стека), то автоинкремент делается только на два, независимо
от формы оператора (то же относится и к регистру
PC, но адресация через PC - особая
тема для разговора).
Пример: пусть в регистре R2 записано число 1000. Тогда команда CLR (R2)+ очистит слово по адресу 1000, а содержимое регистра R2 станет равно 1002. Если затем выполнить команду CLRB (R2)+, то очистится байт по адресу 1002 (младший байт слова), а содержимое R2 станет равно 1003 (R2 указывает на старший байт).
Данный способ адресации обычно применяется для работы с массивами слов или байтов. Если записать в регистр начальный адрес массива, а затем выполнить команду с автоинкрементной адресацией столько раз, сколько элементов в массиве, то команда (одна и та же, выполняемая, например, в цикле) каждый раз будет работать со следующим элементом - обнулять его, пересылать куда-либо и т.п. Если оператор имеет два операнда, как, например, MOV, то автоинкрементную адресацию можно использовать для каждого из них. Пусть в R2 записано число 1000, а в R4 - 2000. Тогда команда MOV (R2)+,(R4)+ перешлёт содержимое ячейки 1000 в ячейку 2000, после чего числа в R2 и R4 будут равны соответственно 1002 и 2002.
Отдельный, «хитрый», случай - автоинкремент регистра PC. Пусть, например, после команды хранится некоторая константа
MOV (PC)+,R4 <константа>
Что произойдёт? <константа> запишется в регистр R4, ведь в момент исполнения команды в PC содержится адрес следующей команды, т.е. числа <константа>! А затем PC увеличится на 2, т.е. управление будет передано уже на следующую после константы команду.
- Автоинкрементная двойная косвенная адресация. Этот способ
не слишком отличается от предыдущего, только в регистре-указателе при
этом содержится не адрес числа, с которым должен работать оператор,
а адрес адреса. Обозначение: @(RN)+.
Пример: пусть в ячейке 1000 записано число 1254, а по адресу 1254 - число 3333. Тогда, если в R2 хранится число 1000, то команда MOV @(R2)+,R4 перешлёт в R4 число 3333, а содержимое регистра R2 станет равным 1002.
Данный способ применяется относительно редко, но тем не менее он очень ценен, так как позволяет работать, например, с таблицами адресов. Пусть каждый элемент таблицы (а) является адресом одного из элементов таблицы (б). Данная адресация позволяет, «двигаясь» по элементам первой таблицы, проверять элементы второй, порядок следования которых может быть произвольным (не совпадать с порядком следования их адресов).
- Автодекрементная косвенная адресация. Если сообщить, что «декремент»
означает уменьшение чего либо, то этот способ будет почти понятен,
не правда ли? У него только два отличия от автоинкрементной косвенной
адресации: во-первых, адрес в регистре-указателе не увеличивается, а
уменьшается; во-вторых, это происходит не после выполнения команды,
а до неё, на что указывает запись -(RN),
в которой знак «-» стоит перед обозначением регистра. В
остальном же сходство с автоинкрементным способом полное, включая то,
что при операциях с байтом модификация числа в регистре делается на
1, а со словом - на 2,
и что регистр SP всегда модифицируется только
на 2.
Пример: пусть в регистре R2 записано число 1002. Тогда команда CLR -(R2) очистит ячейку 1000 (ту, адрес которой на 2 меньше, чем записанное в регистре число), а содержимое регистра после операции, естественно, окажется равным тоже 1000 - оно стало таким ещё до очистки ячейки. Видимо, дальнейших примеров не требуется, по аналогии с автоинкрементным способом всё и так ясно. Этот способ позволяет делать, в общем, то же, что и автоинкрементный, но при работе с массивом его просмотр или пересылка производится «с конца», а перед началом операции в регистре должен быть записан адрес следующей за последним элементом массива ячейки.
Однако этот способ позволяет делать и нечто более интересное - например, перемещать числа в памяти. Пусть в R2 записано число 2004, а по адресу 2002 - число 1254. Тогда команда MOV -(R2),-(R2) поместит число 1254 в ячейку 2000, т.е. как бы «передвинет» его в памяти с большего адреса на меньший. (Разумеется, по адресу 2002 число 1254 тоже сохранится.) Как это произошло? Вначале содержимое R2 уменьшилось на 2, став равным 2002, и регистр указал на число 1254. Теперь оно должно быть записано по адресу второго операнда, но его указатель - тот же регистр, и опять вначале его содержимое уменьшится на 2, станет равно 2000, а уже потом туда будет записано число. Нетрудно догадаться, что таким способом можно «сдвигать» массивы в памяти после удаления в середине какого-либо элемента (аналогичное применение может найти и команда MOV (R2)+,(R2)+).
★ ★ ★
А теперь - маленькое развлечение и одновременно интересный пример по практике использования автодекрементной адресации. Запишем команду MOV -(PC),-(PC) (код 14747). Для большего эффекта поместим её в старшие адреса ОЗУ, в ячейку 77776 (с помощью директив МСД это делается просто и безо всякого ассемблера: 77776А14747И), а затем передадим на неё управление (запустим «программу» по адресу этой команды: 77776G) Что случилось? Экран мгновенно стал «полосатым», а ЭВМ... Она «мертва»: не отвечает на нажатие клавиш и даже на такое «мощное лечебное средство», как клавиша «СТОП», никак не реагирует! Только системный сброс (или выключение, а затем включение питания) «приводит её в чувство». Что же это за «бомба», которая мгновенно парализовала все средства ЭВМ?
После запуска в регистре PC содержится адрес следующей команды - число 100000. При обращении к первому операнду содержимое PC уменьшается на 2, давая адрес 77776, а там записан код команды - число 14747. А куда оно перепишется? По адресу второго операнда, но перед этим содержимое PC уменьшится ещё на 2 и станет равно 77774, т.е. укажет уже на следующую («нижележащую») ячейку памяти. А потом, раз теперь в регистре PC адрес 77774, то туда и будет передано управление, а там снова та же команда, тот же «смертельный» код! Команда как бы «размножается» в памяти ЭВМ, причём со страшной скоростью: чтобы заполнить собой всю память, ей достаточно всего лишь 0.2 секунды! А почему ЭВМ отказывает? Это станет ясно, если вы вспомните, что в зоне адресов 0...777 размещается системная область и стек ЭВМ, её «собственная память». А если стереть содержимое памяти любого «существа», пусть даже такого простого, как БК, это если не смерть, то безумие...
Но что же это всё-таки за команда, зачем мы потратили на её разбор столько времени, и для чего она может пригодиться? Вы, наверное, слышали о компьютерных вирусах, но, возможно, ещё не встречались ни с одним из них. Так вот, позвольте представить: MOV -(PC),-(PC) - простейший вирус (точнее, ещё не настоящий вирус, такие программы носят название «червяк»), вызванное им «заболевание» развивается молниеносно и, безусловно, «смертельно». Эта команда уничтожает всю информацию, хранимую в ОЗУ по адресам, меньшим, чем адрес самой команды. А зачем может понадобиться такая страшная команда? Существует ряд случаев, когда нужно срочно уничтожить хранимую в ОЗУ информацию. Например, чтобы в написанной вами игре после запуска разрешалось только определённое число попыток, а потом её нужно было снова загружать с МЛ (что затруднит нахождение решений), поместите в любом месте программы этот вирус и, когда число попыток исчерпано, заставьте его «ожить» (такой «до поры до времени спящий» вирус носит название «троянский конь»). Сложнее, но тоже возможно реализовать активизацию вируса, например, по истечении заданного времени, после определённого числа копирований программы на МЛ (если она имеет встроенный копировщик) или при попытке что-либо изменить в программе. (Известно, что «настоящие» вирусы на IBM-совместимых компьютерах были изначально созданы для наказания «компьютерных воришек», взламывающих защиты от копирования. - Прим. ред.) Как видим, даже вирусы можно заставить работать с пользой. Но это ещё не всё. Изучая вирусы, создавая их и борясь с ними, программисты совершенствуются уже не в ремесле, а в искусстве. Последнее тоже приносит пользу, хотя отнюдь не перекрывает вред, приносимый вирусами. (Рассуждения о полезности вирусов составляют личное мнение автора статьи. На наш взгляд, написание вирусов можно рассматривать скорее как хулиганство и стремление напакостить всем без исключения (ведь вирус «бьёт» бесприцельно!). Учиться же программировать лучше на других, не менее интересных и полезных, но безвредных вещах. - Прим. ред.)
Ещё одно применение тот же принцип - «уничтожай, размножаясь» - находит в оригинальной компьютерной игре «бой в памяти»: в ОЗУ загружаются специальные «боевые программы», и особый монитор попеременно передаёт им управление, разрешая делать по нескольку «ходов» по очереди. Цель - «вывести из строя» программу противника. Эта игра была очень популярна среди программистов и любителей за рубежом несколько лет назад, но сейчас интерес к ней значительно упал. (О том, как реализовать на БК подобный «многопрограммный режим» с параллельной работой нескольких программ, можно прочитать в журнале «Информатика и образование» №2 за 1992 г. - Прим. ред.)
★ ★ ★
- Автодекрементная двойная косвенная адресация. Обозначение:
@-(RN). Если сделать уже известные нам поправки,
что декремент - это уменьшение, и выполняется оно ДО обращения к ячейке,
на которую указывает операнд, то из автоинкрементной двойной косвенной
адресации легко получается автодекрементная. Далее, не детализируя,
приведём пример. Пусть в регистре R2 записано
число 1002, по адресу 1000
- число 1254, а по адресу
1254 - 3333. Тогда
команда MOV @-(R2),R4
запишет в R4 число 3333.
Данный способ может быть применён для тех же целей, что и автоинкрементная
двойная косвенная адресация, но даёт возможность, скажем, просматривать
таблицу адресов с конца.
Отметим, что такого, казалось бы, логичного способа как «двойная косвенная адресация», записать которую можно было бы, например, как @(RN), не существует. А жаль - он бы тоже мог пригодиться... Но ещё не всё потеряно, мы можем «обмануть» ассемблер и всё-таки задать адресацию таким образом! Как именно - увидим позже.
Кроме того, заметим, что все ранее рассмотренные способы адресации «вписывались» в формат самой команды, и, таким образом, она при переводе в машинный код занимала одно слово. Способы, описываемые ниже, таким преимуществом не обладают. Они требуют задания некоторых дополнительных параметров, в формат одного слова никак не укладывающихся. Такие команды занимают два, а то и три машинных слова, однако потеря объёма памяти компенсируется ещё большей гибкостью и универсальностью средств программирования на ассемблере. Но прежде чем перейти к описанию этих способов адресации, введём ещё два общих обозначения: X - восьмеричное число, MET - произвольное имя метки.
- Индексная адресация. Это разновидность косвенной адресации
через регистр. Обозначение: X(RN)
или MET(RN). Стоящее
перед регистром восьмеричное число X или имя
метки MET называется индексом, указывает
смещение от адреса, содержащегося в регистре, и заносится при
трансляции вторым или третьим словом команды. Восьмеричное число может
быть записано как без знака, так и (в последних версиях ассемблеров)
со знаком «-» (в этом случае при трансляции оно автоматически
переводится в дополнительный код). Вместо имени метки ассемблер тоже
подставляет число - её абсолютный адрес, полученный в результате трансляции
и компоновки. Адрес же ячейки, к которой мы обращаемся, при данном способе
адресации определяется как сумма смещения (индекса) и содержимого регистра
RN (так называемой базы). Пример: пусть
в регистре R2 записано число
2000, тогда команда CLR
1000(R2) обнулит
слово по адресу 3000, а
CLR -1000(R2)
-слово по адресу 1000.
Этот способ может быть использован, если при одном и том же содержимом регистра, которое нежелательно почему-либо изменять, нужно выполнить действия с несколькими ячейками, отстоящими от заданного в регистре адреса на некоторые фиксированные «расстояния». Например, если задать в R2 адрес какого-либо байта экранного ОЗУ, то команда CLRB 100(R2) очистит байт, находящийся в следующей телевизионной строке (под заданным), а CLRB 1200(R2) -байт, лежащий ниже на 10д строк.
Ещё интереснее случай использования в качестве индекса имени метки. Предположим, что нам нужно вместо заносимых в регистр R2 чисел (например 0, 1, 2, ... 7) подставить значения 4, 3, 2, 1, 0, 7, 6 и 5 соответственно. Эта задача называется перекодировкой. Можно решить её «в лоб» - проверяя каждый раз содержимое регистра на соответствие всем значениям ряда по очереди. Но такой способ ужасно неэкономичен как по времени, так и по занимаемому объёму ОЗУ (представьте, что возможных кодов было бы 1000). Лучше применить табличный метод. Напишем программу, состоящую всего из одной (!) команды MOVB TAB(R2),R2 и составим таблицу перекодировки, которая в нашем примере будет иметь вид:
TAB: .B:4,3,2,1,0,7,6,5
(Забегая вперёд, поясним, что псевдокоманда .B: позволяет записать ряд чисел в последовательные байты памяти, начиная с текущего адреса, в нашем случае - с адреса метки TAB.)
Поставленная задача решена. Пусть, например, в регистре R2 содержится число 3. Адрес первого операнда команды будет вычислен как сумма адреса метки TAB и содержимого регистра. Отсчитаем, начиная с нуля, третий байт таблицы - это число 1. Оно и будет занесено вместо исходного числа 3 в регистр R2 - перекодировка выполнена.
Вообще же круг подобных задач весьма широк - от замены символов текста (например, если набор символов, которые печатает принтер, ограничен или их порядок в знаковой таблице не соответствует принятому в БК-0010) до вычисления значений произвольной функции по её аргументу табличным методом. (Закодировав значения синуса и косинуса для углов от 0 до 2π с некоторым шагом, таким способом можно реализовать вычисление координат точек окружности (или дуги) с заданным радиусом и, соответственно, ассемблерную подпрограмму для её вычерчивания. - Прим. ред.)
- Индексная косвенная адресация. Обозначение: @X(RN)
или @MET(RN). По
смыслу она близка к предыдущей, но в ячейке, определяемой суммой индекса
и содержимого регистра, теперь находится не само число-операнд, а
его адрес. С таким явлением - двойной косвенностью - мы уже встречались,
поэтому приводить примеры не будем (читатель может придумать их сам),
а лучше выполним данное ранее обещание - покажем, как реализовать через
регистр двойную косвенную адресацию, которая в наборе команд ЭВМ «официально»
не существует. Как уже говорилось, логично было бы представить запись
такой адресации в виде @(RN), мы же вместо
этого запишем @0(RN)
Цель достигнута - мы обратились к ячейке с двойной косвенностью и без
модификации содержимого регистра-указателя. Правда, за этот «обман»
придётся платить - команда занимает целых два машинных слова, хотя содержимое
второго из них всего лишь нуль.
Индексная косвенная адресация применяется редко, но иногда без неё почти невозможно обойтись. Например, когда нужно не просто перекодировать последовательность чисел, а сравнить её с другой последовательностью (возможно, тоже перекодированной!). С такой необходимостью вы непременно столкнётесь, если захотите написать программу сортировки по алфавиту русских слов - ведь символы русского алфавита в БК не упорядочены по возрастанию кодов.
- Непосредственная адресация. Этот способ позволяет в качестве
одного из операндов использовать число - константу, которая записывается
как #Х или #MET.
Значок «#», называемый на программистском жаргоне «решёткой»,
в ассемблере как раз и обозначает «число». Восьмеричное
число- операнд может быть без знака или со знаком «-», а
имя метки ассемблер при трансляции и компоновке заменяет на её абсолютный
адрес, как и в двух предыдущих способах. Полученный при этом числовой
операнд записывается вторым или третьим словом команды.
Ясно, что при этом способе часть команд, формально возможных, теряет смысл. Например, практически бесполезна операция CLR #1000. Что мы тут можем обнулить? Только второе слово самой команды, а зачем? Или, скажем, команда MOV #1000,#2000. Она всего лишь перешлёт второе слово по адресу третьего. Трудно вообразить программу, в которой это требуется... Но пора, наверное, перейти к примерам «более практического свойства». Пожалуйста:
MOV #1000,R2 ; Запись в R2 числа 1000 MOVB #40,R0 ; Запись в R0 числа 40
Обратим внимание, что во второй строке мы записываем число 40 в регистр R0 с помощью «байтовой» команды. Как это понимать, разве мы можем адресоваться не ко всему регистру, а к его части? Да, можем. Если при регистровой адресации применена байтовая команда, используется младший байт регистра. Раз так, то, казалось бы, его старший байт при этом меняться не должен. Но не тут-то было! Если с младшим байтом оперирует команда MOVB, происходит не всегда полезная вещь, называемая распространением знака - все биты старшего байта регистра-приёмника (второго операнда) принимают значение знакового разряда младшего байта (т.е. разряда 07). В приведённом примере (MOVB #40,R0) все разряды старшего байта R0 обнулятся, даже если мы не преследовали такой цели. Если выполнить команду MOVB #377,R0, в регистре R0 окажется не 377, а 177777 (во все разряды старшего байта R0 будут занесены единицы). А вот после MOV #377,R0 в R0 окажется число 377, как и следовало ожидать. Вообще же распространение знака возникает, если число в младший байт регистра будет записано с применением любого способа адресации в команде MOVB. Это, видимо, единственный случай, когда младший байт слова оказывает какое-то влияние на старший. (Кроме «явных» ассемблерных команд: обмена местами старшего и младшего байтов (SWAB) и небайтовых команд смещения вправо и влево (ASL, ASR, ROL и ROR). - Прим. ред.) При байтовых операциях с ячейками ОЗУ такого, конечно же, не бывает.
Продолжим примеры. Нижеследующая запись уже в какой-то степени напоминает осмысленный текст программы:
MOV #TEX,R2 ; Запись адреса метки ТЕХ в R2 MOVB #101,(R2)+ ; и запись в последовательные MOVB #102,(R2)+ ; ячейки памяти, начиная с метки MOVB #103,(R2)+ ; ТЕХ, чисел 101, 102 и 103 ; (кодов символов "А","В","С")
При написании программ часто приходится с помощью непосредственной адресации засылать в регистры (реже - в ячейки памяти) коды символов (как это и сделано в приведённом примере). Для этого их надо помнить наизусть, или, по крайней мере, иметь под рукой таблицу кодов. Развитые версии ассемблеров (в том числе, конечно, и МИКРО.10К) предоставляют дополнительное удобство- возможность записи в качестве операндов самих символов. Это тоже разновидность непосредственной адресации. Символы (они могут быть любыми) должны быть заключены в апострофы, а их число не должно быть больше двух.
MOV 'a',R0 ;Примеры записи в регистр R0 MOV '12',R0 ;кодов символов: MOV 'A=',R0 ;"а", "1" и "2", "А" и "="
Если при такой адресации в апострофах записан один символ, то его код заносится в младший байт регистра (или ячейки памяти), если два - код первого заносится в младший байт, а второго - в старший. (Коды, не имеющие символьного представления, например перемещения курсора, управление выводом на дисплей и т.д., приходится вводить «по старинке» - с помощью #<КОД>. - Прим. ред.)
- Абсолютная адресация. Этот способ прост и прямолинеен: прямо
указывается абсолютный адрес ячейки памяти, к которой мы обращаемся.
Обозначение: @#Х. Восьмеричное число
X помещается при трансляции во второе или
третье слово команды. Пример:
CLR @#156 ; Очистка слова по адресу 156 CLRB @#45 ; Очистка байта по адресу 45 MOV @#100112,R2 ; Запись содержимого слова ; по адресу 100112 в R2 MOV #137,@#1000 ; Запись числа 137 по адресу 1000
Данный способ достаточно широко применяется для обращения к ячейкам вне самой программы, например по адресам системной области или ПЗУ. Обращаться же к ячейкам, находящимся в ОЗУ пользователя (или тем более в самой программе), удобнее с помощью относительной адресации.
- Относительная адресация. Этот способ является, пожалуй, самым
распространённым при задании адресов переходов и вызове подпрограмм
(что мы рассмотрим в своё время), а также при обращении к ячейкам памяти
(переменным), расположенным в ОЗУ пользователя, особенно в пределах
самой программы. Обозначение: MET. При таком
обращении ассемблер во время трансляции вычисляет смещение (разность
между адресами текущей команды и метки MET)
и записывает это смещение вторым или третьим словом команды. При исполнении
программы адрес операнда определяется как сумма текущего содержимого
счётчика команд PC и смещения. Последнее вычисляется
в дополнительном коде с учётом знака, поэтому такая адресация возможна
как «вперёд», так и «назад» относительно
текущей команды. Не вдаваясь в тонкости, которые больше волнуют создателей
ассемблер-систем, чем их пользователей, можно сказать, что относительная
адресация - это просто обращение к ячейке памяти, помеченной меткой
MET. Для ассемблеров БК-0010, имеющих механизм
локальных меток, необходимо иметь в виду, что адресация (не только в
этом, но и во всех других случаях, когда в составе операндов употребляется
имя метки) возможна только с использованием обычных меток (начинающихся
с буквы), а локальные пригодны лишь для указания адресов переходов в
операторах ветвления и цикла. Это связано в основном с тем, что отличить
локальную метку (начинающуюся с цифры) от числа (например, индекса)
ассемблер-система не может. Пример:
CLR А1 ; Адресация с использованием MOV R2,BR2 ; меток: MOV RGB,(R5)+ ; A1, BR2, RGB, TYP MOVB #377,TYP MOVB TYP,R4
Особо отметим, что метки, упоминаемые в операндах (при любом способе адресации), должны быть определены, т.е. должны присутствовать в тексте программы, помечая собой какие-либо строки (или же должны быть заданы оператором прямого присваивания, о котором речь пойдёт дальше). При использовании имён неопределённых (отсутствующих) меток правильная компоновка программы невозможна.
- Относительная косвенная адресация. Аналогична предыдущему способу, но по адресу, определяемому меткой, находится не само число-операнд, а, как обычно и случаях косвенной адресации, его адрес. Обозначение: @MET. Применяется весьма редко, например для передачи управления по адресу, который перед этим занесён в определённую ячейку памяти (т.е. по значению переменной). Практически всегда этот способ адресации может быть заменён на другой, (например регистровую косвенную адресацию), по иногда он довольно удобен, так как не требует использования регистра.
★ ★ ★
На этом можно было бы считать изучение способов адресации законченным и в заключение привести сводную таблицу по всем рассмотренным способам. Но сначала сделаем заявление, на первый взгляд, противоречащее всему вышеизложенному: теоретически возможных способов адресации всего восемь, в общем виде все они так или иначе используют один из регистров общего назначения, т.е. являются разновидностями регистровой адресации. Но позвольте, скажете вы, как же тогда быть с тем, что мы рассмотрели 12 способов, и в четырёх последних имена регистров вообще не фигурируют? Дело в том, что эти четыре способа являются не самостоятельными, а лишь разновидностями первых восьми. Они выделены потому, что имеют большое практическое значение, и по этой же причине в языке ассемблера для них введены особые, «нестандартные» обозначения. Между тем, смысл этих способов сводится к тому, что в качестве регистра-указателя используется R7 (или PC) - счётчик команд. Проиллюстрируем это па примере. Как мы уже знаем, непосредственная адресация обозначается как #Х, где X - число-операнд (для простоты не будем касаться меток). Возьмём конкретную команду: MOV #1000,R2. При трансляции ассемблер заносит число 1000 во второе слово. Но мы уже сталкивались с случаем, когда, применяя регистр PC и автоинкрементную адресацию, можно было прочитать записанную после команды константу! Итак, непосредственная адресация - это вовсе не новый метод, а всё тот же самый способ с автоинкрементом PC. И вместо MOV #1000,R2 вполне можно записать:
MOV (PC)+,R2 .#1000
(Здесь .#1000 - это запись числа 1000 по текущему адресу.)
Совершенно так же можно «развенчать» и остальные три способа - абсолютной, относительной и относительной косвенной адресации (предоставляем это сделать читателю). Всё это всего лишь удобные приёмы записи текста программ на языке ассемблера, частные случаи «настоящих» способов адресации с использованием PC. Именно так мы их и приведём в таблице.
Основные способы адресации
Номер (код) |
Наименование |
Обозначение |
---|---|---|
0 |
Регистровая (прямая) |
RN |
1 |
Регистровая косвенная |
@RN |
2 |
Автоинкрементная косвенная |
(RN)+ |
3 |
Автоинкрементная двойная косвенная |
@(RN)+ |
4 |
Автодекрементная косвенная |
-(RN) |
5 |
Автодекрементная двойная косвенная |
@-(RN) |
6 |
Индексная |
X(RN) или MET(RN) |
7 |
Индексная косвенная |
@X(RN) или @MET(RN) |
пособы адресации с использованием PC
Код |
Наименование |
Обозначение |
|
---|---|---|---|
стандартное |
специальное |
||
2 |
Непосредственная |
(PC)+ |
#Х |
3 |
Абсолютная |
@(PC)+ |
@#Х |
6 |
Относительная |
MET(PC) |
MET |
7 |
Относительная косвенная |
@MET(PC) |
@MET |
Как видим, действительно ничего принципиально нового в последних четырёх способах адресации нет. Но их так называемая «стандартная» запись для программистов на ассемблере выглядит в высшей степени необычно и почти никогда не применяется.
Можно ли «изобрести» ещё способы с использованием PC? Да, конечно. Но практического значения они не имеют и потому или не применяются, или мы их используем, даже не задумываясь, что у них есть столь «именитые родичи», имеющие отдельные «титулы» и «гербы». Продемонстрируем такие «открытия». Допустим, мы по аналогии захотели создать способ с применением PC, имеющий код 0. Что мы получим? Например, MOV PC,R4. Что же тут нового? Или, скажем, код 1: MOV @PC,R4 - так мы просто запишем в R4 код следующей команды. Это нужно? Мягко говоря, не часто... Код 4: MOV -(PC),-(PC) - вирус, с которым мы уже знакомы. И наконец, код 5: MOV @-(PC),R4 - эта команда перепишет в R4... число из ячейки с адресом, равным коду текущей команды! Чепуха, и ничего более.
Контрольные вопросы и задания
- Что будет записано в регистр R0 командой
MOV R1,R0,
если в регистре R1 содержится число
5555?
-
5555.
-
- Что будет записано в регистр R5 последовательностью
команд:
MOV #1000,@#2500 MOV #40000,@#1000 MOV @#2500,R4 MOV R4,R5 MOV @R5,R5
-
Число 40000.
-
- Что будет записано в R0 последовательностью
команд (считая, что метка MET определена и
находится в ОЗУ):
MOV #1000,MET MOV #3402,@#1000 MOV #377,@#3400 MOV @MET,R5 MOV -(R5),R0
-
Число 377.
-
- Каким будет содержимое R2 после выполнения
каждой из команд: MOVB #160,R2;
MOV #277,R2;MOVB
#201,R2? (Чтобы
не ошибиться, «нарисуйте» полученные числав двоичной
форме, поразрядно.)
-
160, 277, 177601.
-
- Команда MOV -(PC),-(PC) «саморазмножается» в ОЗУ в сторону младших адресов («вниз» по памяти). Придумайте команду, которая будет исполнять «бег на месте», т.е. переписывать свой код по своему же адресу и передавать себе управление.
- Придумайте последовательность двух одинаковых команд, исполняющую «бег на месте» (обязательно с использованием кодов обеих команд).
- Придумайте команду, которая бы «саморазмножалась»
по памяти «вверх».
-
Такой команды нет.
-
- Что запишет в R3 последовательность команд:
MOVB #255,R0 CLR MET MOVB R0,MET MOV (PC)+,R3 MET: CLR R0
-
Число 255.
-
MOV -(PC),@PC
MOV @PC,-(PC) MOV @PC,-(PC)
Оператор прямого присваивания. Выражения
После того, как изучены способы адресации, рассмотрим ещё два тесно примыкающих к ним вопроса. Мы уже знаем, что метками в тексте программы помечаются некоторые строки и что в процессе трансляции и компоновки метке присваивается текущий адрес (т.е. адрес начала строки в которой она стоит). Но как быть, если имеется в виду адрес в ПЗУ или в системной области? Может быть, в таких случаях придётся ограничиться только абсолютной адресацией, прямо указывая адрес? Это не всегда удобно, поэтому в ассемблере предусмотрен специальный механизм для определения меток, так называемый оператор прямого присваивания. Он записывается как MET=Х, где X - восьмеричное число без знака. Этот оператор нетранслируемый (при трансляции он не преобразуется в машинный код, а только присваивает заданной метке значение адреса X, которое заносится в таблицу меток и используется при дальнейшей трансляции и компоновке). Операторы прямого присваивания принято размещать в начале каждого отдельного модуля программы.
Что же можно определить с помощью этого оператора и для чего? Прежде всего, особенности работы ассемблер-системы таковы (не будем вдаваться в подробные объяснения, но сделано это для обеспечения перемещаемости программ), что обращение по адресам системной области (0 - 777) и ПЗУ (100000- 177777) возможно только с применением абсолютной адресации или - косвенно - через регистр. Относительная адресация этих ячеек невозможна. Вот эти-то абсолютные значения адресов и могут быть приравнены меткам с помощью оператора прямого присваивания, причём при обращении по адресу определённой таким образом метки адресация всё равно получается не относительная, а абсолютная. С другой стороны, оператор прямого присваивания не может приравнять метке адрес, лежащий в пределах ОЗУ пользователя - при трансляции и компоновке это обернётся ошибкой. Пример (фрагмент листинга реальной программы - драйвера принтера):
REG=57 ; Признак регистра CPR=256 ; Копия порта POZ=374 ; Позиция печати POR=177714 ; Порт ввода-вывода ; Выход в Фокал или МСД ;.......... CLRB REG ; Per. кот. сбросить ;.......... CLR POZ ; Обнулить позицию ;.......... CLR POR ; Очистить порт ;.......... MOV R0,CPR ; Запомнить символ
Здесь показано как определение меток в начале текста с помощью операторов прямого присваивания, так и дальнейшее использование этих меток. Отметим, что определённая таким способом метка может быть использована в составе команды с любым методом адресации (не только относительным) и ведёт себя как обычная переменная. Например, если бы мы записали MOV #POR,R4, то тем самым число 177714, присвоенное метке POR, было бы записано в регистр R4.
Развитые версии ассемблеров допускают адресацию операндов с помощью не только меток, но и выражений, состоящих из термов. (Термами называются входящие в состав выражения числа или символы, связанные знаками арифметических операций «+» или «-».) Количество термов в выражении для большинства версий ассемблеров не должно превышать двух. Выражение записывается как MET+Х или MET-Х. и в процессе трансляции вычисляется его значение. Пример (адрес метки ТЕХ равен 1100):
CLR TEX ;Очистка ячейки 1100 CLR ТЕХ+100 ;Очистка ячейки 1200 CLR ТЕХ-100 ;Очистка ячейки 1000
Аналогично можно задать операнды с помощью выражений и в других случаях, например:
CLR MET+10(R2) MOV #MET-122,R2 CLRB @MET+43
(При трансляции и компоновке вторым словом данных команд записывается результат вычисления выражений.)
Контрольные вопросы и задания
- Что запишется в регистр R1 в результате
выполнения программы.
MET=254 MOV #MET+4,R0 MOV #155,-(R0) MOVB @#256,R1
-
Число 155.
-
- Какая ячейка будет обнулена в результате выполнения программы:
MET=100 MOV #150,R0 MOV #1400,MET+156 CLR @MET+6(R0)
-
Ячейка с адресом 1400.
-
Операторы ассемблера
Операторы (команды) определяют, что программа должна делать и в какой последовательности. Операторов в составе ассемблера много, и их можно классифицировать по-разному. Самой простой и удобной для изложения, видимо, является классификация по функциям:
- вычислительные операторы (одно- и двухоперандные),
- операторы управления программой,
- операторы управления машиной,
- прочие.
При описании операторов мы будем приводить форму их записи в общем виде следующим образом. Если оператор может работать и с байтами, и со словами, то дополнение его имени для работы с байтами будет приводиться в скобках, например MOV(B) Если в составе команды допустимы любые способы адресации, то операнды в общем виде будут обозначаться как А,В (А - операнд-источник, в процессе работы команды не меняющийся, а В - операнд-приёмник, куда заносится результат операции). Если один и тот же операнд является и источником, и приёмником, то обозначим его как АВ. Если в процессе выполнения команды содержимое операндов не меняется (оба они как бы являются источниками), будем обозначать их А1,А2 Если операнд в составе команды используется только как адрес перехода, обозначим его в общем виде как N. Если же описываемый оператор допускает не любые способы адресации, а лишь некоторые, это будет отмечаться особо, причём для обозначения этих способов будут использоваться уже знакомые нам символы RN, X, MET.
Перед тем как перейти к описанию конкретных операторов, кратко напомним, что кроме восьми регистров общего назначения в составе процессора имеется ещё один служебный регистр PS или регистр слова состояния процессора (ССП), с которым вы уже знакомы. При описании операторов нас будут интересовать 4 младших разряда (бита) ССП - так называемые флаги условий. Вспомним, что они обозначаются буквами N, Z, V, С и значение каждого из них устанавливается равным нулю или единице в зависимости от результата выполнения очередной команды. Эти биты используются для реализации условных переходов и для некоторых других целей. Их значение устанавливается равным единице в следующих случаях:
- N=1, если результат операции - отрицательное число;
- Z=1, если результат операции - ноль;
- V=1, если при операции произошло переполнение (т.е. перенос в знаковый разряд);
- С=1, если при операции произошёл перенос (т.е. выход единицы за пределы слова или байта).
В противных случаях соответствующие биты ССП равны нулю.
1. Вычислительные операторы
С одним операндом
- CLR(B) В - очистка. Обнуляет слово (байт) операнда, записывая в него нуль, независимо от исходного содержимого. Используется для обнуления (сброса) программных счётчиков, признаков, сумматоров, подготовки ячеек и т.п.
- COM(B)
АВ - поразрядное инвертирование. Все
биты операнда независимо друг от друга заменяются на противоположные
(нуль на единицу, а единица - на нуль). Пусть в регистре
R4 записано число 1 011
101 100 111 000 (восьмеричное 135470).
После выполнения команды COM
R4 в регистре R4
будет записано 0 100 010 011 000 111 (восьмеричное
042307).
Другое название данной операции - дополнение до единицы, так как сумма исходного значения и результата операции во всех разрядах содержит единицы: АВ+COM(АВ)=1 111 111 111 111 111.
- INC(B) АВ - инкремент. К операнду прибавляется 1.
- DEC(B)
АВ - декремент Из операнда вычитается
1.
При исполнении операторов INC и DEC, а также некоторых других (ASR, ROL, ADD, SUB и пр.) может произойти перенос за пределы слова или байта, например при выполнении последовательности команд:
MOV #177777,R0 INC R0
Однако флаг переноса (С-разряд) при этом не устанавливается, что необходимо учитывать при использовании данных операторов вместе с операторами условного перехода.
- NEG(B)
АВ - инверсия знака. Число заменяется
на отрицательное, равное прежнему по абсолютной величине, но представленное
в дополнительном коде.
Другое название данной операции - дополнение до двух, так как сумма исходного числа и результата равна нулю плюс перенос «за пределы» слова (т.е. устанавливается флаг переноса): AB+NEG(AB)=0+перенос.
(При использовании оператора NEG(B) значение 1000008 заменяется самим собой, так как в записи с дополнением до двух для наибольшего отрицательного числа 1000008 нет соответствующего положительного - Прим. ред.)
- TST(B) A - проверка содержимого операнда. Операнд при этом не изменяется, но биты условий ССП устанавливаются в соответствии с его содержимым. Например, если операнд равен нулю, то устанавливается в единицу бит Z, а если отрицателен, то N.
Кроме проверки операнда оператор TST(B) иногда применяется для инкремента (или декремента) содержимого регистра на 2. Например, команда TST (R2)+ увеличивает на 2 содержимое R2, причём делает это быстрее, чем две команды INC R2, и занимает в ОЗУ всего одно слово. Однако при таком использовании данного оператора в регистре должно быть записано число, соответствующее реально существующему адресу ЭВМ. В противном случае, например если в регистре R2 записано число 177776 (не существующий в БК-0010 адрес), произойдёт прерывание по зависанию. (Это замечание, конечно, относится к любой команде, выполняемой по косвенному адресу, независимо от того, с какой целью она применяется.) Необходимо также отметить, что на некоторых ЭВМ с аналогичной системой команд косвенное обращение к слову по нечётному содержимому регистра тоже ведёт к зависанию. На БК-0010 вместо этого производится обращение по меньшему чётному адресу, т.е. адрес обращения корректируется. Содержимое же регистра после такой операции (при автоинкрементной или автодекрементной адресации), как обычно, изменяется на 2, т.е. остаётся нечётным.
- ASL(B)
АВ - арифметический сдвиг влево. Слово
(или байт) операнда поразрядно сдвигается влево, старший разряд при
этом переписывается в бит С ССП, а в младший
разряд заносится ноль. Пусть, например, в регистре
R2 записано слово 1 011
101 100 111 000 (восьмеричное 135470).
После выполнения команды ASL
R2 получим: 0 111 011 001
110 000 (восьмеричное 073160), разряд
С=1.
Оператор ASL(B) применяется для умножения на 2 и других целей.
- ASR(B)
АВ - арифметический сдвиг вправо. Слово
(или байт) операнда поразрядно сдвигается вправо, младший разряд при
этом переписывается в бит С ССП, старший разряд
не меняется (т.е. не меняется знак числа. - Прим. ред.). Пусть,
например, в регистре R2 хранится слово
1 011 101 100 111 000 (восьмеричное
135470). После выполнения команды
ASR R2 получим:
1 101 110 110 011 100 (восьмеричное
156634), разряд С=0
Оператор ASR(B) применяется для деления на 2 и других целей.
- ROL(B) AB - циклический сдвиг влево. Слово (или байт) операнда поразрядно сдвигается влево, старший разряд при этом переписывается в бит С ССП, а предшествующее содержимое последнего переходит в младший разряд операнда. Слово или байт как бы замыкается при этом в кольцо (причём замыкающим звеном является бит С ССП) и происходит вращение этого кольца на один разряд влево.
- ROR(B)
AB - циклический сдвиг вправо. Слово
(или байт) операнда поразрядно сдвигается вправо, младший разряд при
этом переписывается в бит С ССП, а предшествующее
содержимое С переходит в старший разряд операнда
(аналогично оператору ROL(B),
но с вращением в другую сторону).
Два последних оператора могут также применяться для поразрядной перезаписи одного операнда в другой через С-разряд. Примеры:
ROL R2 ; старший бит содержимого R2 ROR R4 ; переносится в старший бит R4 ROL R2 ; старший бит R2 ROL R4 ; переносится в младший бит R4 ROR R2 ; младший бит R2 ROL R4 ; переносится в младший бит R4 ROR R2 ; младший бит R2 ROR R4 ; переносится в старший бит R4
Выполняя такие сочетания команд подряд несколько раз, можно «выворачивать наизнанку» слово или его часть, выделять нужное число разрядов в другое слово и т.д.
- ADC(B) AB - прибавление разряда переноса. К операнду прибавляется содержимое бита C ССП. Применяется для учёта переноса при сложении. умножении, вычислении контрольных сумм и для других целей.
- SBC(B) AB - вычитание разряда переноса. Из операнда вычитается содержимое бита C ССП. Применяется для учёта переноса при вычитании, делении и для других целей.
(Оператор ADC(B) может быть использован для обеспечения многократной точности при сложении чисел:
ADD А0,B0 ; сложение младших частей ADC В1 ; прибавить перенос к старшей части ADD А1,В1 ; сложение старших частей
SBC(B) аналогично используется для обеспечения многократной точности при вычитании:
SUB А0,B0 SBC В1 SUB A1,B1
В данном примере показано вычитание с двойной точностью. - Прим. ред.)
- SWAB АВ - перестановка байтов. Старший и младший байты операнда меняются местами. Применяется для побайтного доступа к содержимому слова и для других целей.
- SXT АВ - расширение знака. Всем разрядам операнда (слова) присваивается значение знакового разряда. В результате операции слово может стать равным или 0 (если исходное число положительное), или 177777 (если отрицательное). Применяется для операций с плавающей запятой, учёта знака и для других целей.
- MFPS В - чтение ССП. Содержимое ССП записывается в операнд. О структуре ССП подробно говорилось в разделе, посвящённом описанию центрального процессора. Применяется этот оператор очень редко, в основном для сохранения текущего ССП с целью его последующего восстановления или анализа. Сохранение и анализ ССП при вызовах подпрограмм и прерываний, а также при условных переходах ЦП обычно осуществляет автоматически, поэтому данная команда требуется только тогда, когда обработка ССП отличается от стандартной.
- MTPS А - запись ССП. Слово-операнд записывается в ССП. Применяется чаще всего для задания необходимого уровня приоритета, реже для восстановления ССП после нестандартного преобразования. Нужно отметить, что команды MTPS и MFPS являются байтовыми, при косвенной автоинкрементной или автодекрементной адресации через регистр его содержимое будет меняться на 1, а не на 2, а при записи содержимого ССП в регистр его старший байт будет изменяться, как это описано ниже для команды MOVB.
С двумя операндами
- MOV(B) A,B - пересылка. Содержимое операнда А записывается (копируется) в операнд В. Содержимое А не меняется, исходное значение B теряется. Этот оператор, особенности его работы и примеры были подробно рассмотрены раньше. Напомним только, что при байтовой пересылке В регистр в нём происходит распространение знака младшего байта на весь старший байт (подробно этот эффект рассмотрен в одном из предыдущих номеров журнала. - Прим. ред.). Оператор MOV - наиболее часто используемый из всех операторов ассемблера.
- BIC(B)
A,B - очистка
разрядов по маске. Во втором операнде обнуляются биты (разряды)
по маске первого. Смысл этого действия следующий: во втором операнде
(слове или байте) устанавливаются равными нулю те разряды, которым
соответствуют единицы в первом операнде. Пусть в регистре
R2 хранится слово 0 100
001 111 010 100, а в регистре R4 -
0 110 100 000 100 111. Выполнив команду
BIC R2,R4,
получим в регистре R4 число
0 010 100 000 100 011 - биты
R4, которым соответствуют единицы в
R2, обнулены, остальные не изменились.
Ещё один пример. После выполнения команд
MOV #177777,R4 BIC #177400,R4
содержимое R4 станет равно 377 (выделен младший байт R4).
Оператор BIC(В) широко применяется для выборочного стирания отдельных разрядов слова (байта) с целью выделения нужных разрядов и т.п.
- BIS(B)
A,B - установка
разрядов по маске. В отличие от предыдущего оператора, во втором
операнде устанавливаются в единицу те разряды, которым соответствуют
единицы в первом. Для тех же исходных данных, что в предыдущем
примере, результатом выполнения команды BIS
R2,R4 будет содержимое
R4:0 110 101 111 110 111,
а последовательность команд
CLR R4 BIS #377,R4
даст содержимое R4, равное 377.
Оператор BIS(B) широко применяется для выборочной записи определённых разрядов, «наложения» операндов и т.п.
- BIT(B) А1,А2 - проверка битов по маске. Проверяются биты (разряды) второго операнда по маске первого. Оба операнда не меняются, но по результатам проверки устанавливаются биты условий ССП. Смысл операции следующий. Если хотя бы одному разряду первого операнда, равному единице, соответствует единица в том же разряде второго операнда, результат не нулевой. Если же всем единицам первого операнда соответствуют только нули второго, результат нулевой. Чаще всего оператор используется для проверки какого-либо отдельного бита, для чего в первом операнде задаётся маска, в которой присутствует только одна единица в соответствии с проверяемым разрядом второго. Например, если нажата любая клавиша, в системном регистре 177716 разряд 06 будет равен 0, в противном случае - единице. Тогда для выявления факта нажатия клавиши можно применить команду BIT #100,177716. Если клавиша нажата, результат исполнения команды равен нулю и наоборот.
- CMP(B) А1,А2 - сравнение. Операнды сравниваются между собой и по результатам сравнения устанавливаются биты условий ССП. Сами операнды при этом не меняются. Сравнение осуществляется псевдовычитанием второго операнда из первого, т.е. как бы осуществляется действие (А1-А2). Например, если операнды равны, то результат операции - нуль (Z=l), если второй операнд больше первого, то результат отрицательный (N=1) и т.д. Оператор применяется очень широко как для сравнения операндов, так и для инкремента (или декремента) регистров общего назначения, причём он позволяет модифицировать содержимое регистра сразу на 4 (например CMP (R2)+,(R2)+ ), ничего при этом не меняя, кроме содержимого R2 и битов условий, и занимая всего одно слово. Но необходимо помнить, что биты условий при таком использовании не имеют никакого отношения к содержимому регистра и обнаружить после такой команды, например, произошло ли переполнение R2, невозможно. Кроме того, содержимое регистра должно указывать на реально существующий адрес, иначе произойдёт зависание.
- ADD А,В - сложение. Содержимое операндов (слов) суммируется, результат записывается во второй операнд, первый же при этом не меняется, т.е. выполняется присваивание В=В+А. Операция широко применяется для сложения и умножения.
- SUB А,В - вычитание. Из второго операнда вычитается первый, результат записывается во второй операнд, первый не меняется, т. е. выполняется присваивание В=В-А. Необходимо всегда помнить, что порядок вычитания в операторе SUB (из второго - первый) обратен псевдовычитанию в операторе CMP (из первого - второй), это часто путают. Оператор SUB широко применяется для вычитания и деления, иногда - для сравнения.
- XOR RN,B - поразрядное «исключающее или». Выполняется логическая функция «исключающее или» между соответствующими разрядами (битами) первого и второго операндов, результат заносится во второй операнд. Первый операнд может быть только регистром общего назначения, второй адресуется любым способом.
Что такое «исключающее или»? Это функция, фиксирующая несовпадение битов, так называемая функция неравнозначности. Поясним сказанное таблицей состояний.
1-й бит |
2-й бит |
Результат |
---|---|---|
0 |
0 |
0 |
0 |
1 |
1 |
1 |
0 |
1 |
1 |
1 |
0 |
Подобная операция выполняется между всеми разрядами операндов, в результате чего получается число, содержащее не разность, а как бы разницу между ними. Одинаковые операнды дают в результате ноль, взаимно-инверсные - число 177777. Такая функция даёт возможность «наслаивать» одно число на другое, например формировать на экране спрайты на фоне другого изображения с сохранением последнего. Если вывести то же самое изображение с помощью функции XOR на то же место вторично, то оно исчезнет, а фон восстановится. Аналогично можно «наслаивать» звуки в многоголосных мелодиях, воспроизводимых одним динамиком, и т.п. Пусть в R2 записано число 0 101 110 111 001 011, a в R4 - 1 001 100 111 101 011. Тогда после выполнения команды XOR R2,R4 в R4 будет записано 1 100 010 000 100 000.
2. Операторы управления программой
Эта группа операторов предназначена для управления ходом вычислительного процесса (организации ветвлений, подпрограмм и циклов).
- BR MET - безусловное ветвление. Вызывает переход (передачу управления) по адресу MET независимо от битов условий. Метка, по которой выполняется переход, может быть как обычной, так и локальной.
Здесь, как и во всех других командах ветвления, адрес передачи управления определяется суммой текущего значения счётчика команд PC и младшего байта команды, в который при трансляции заносится разность (#MET-PC) в дополнительном коде, делённая на 2, - смещение. При исполнении команды ветвления смещение умножается на 2 с учётом знака и прибавляется к содержимому PC. Таким образом обеспечивается передача управления на расстояние до 256д байт «вперёд» или «назад», считай от текущего адреса.
Все остальные команды ветвления (кроме BR) являются условными, т.е. передача управления по адресу MET осуществляется, если биты условий, установившиеся после выполнения предшествующей команды в разрядах 00...03 ССП, удовлетворяют условиям оператора ветвления. В противном случае программа выполняется дальше, а оператор ветвления игнорируется. Сами по себе операторы ветвления не меняют биты условий, поэтому можно располагать их друг за другом для последовательной проверки нескольких условий. Удобно привести все операторы ветвления в виде таблицы:
Команда |
Условие ветвления |
Определяющие биты условий |
---|---|---|
BNE MET |
не равно нулю |
Z=0 |
BEQ MET |
равно нулю |
Z=1 |
BPL MET |
плюс |
N=0 |
BMI MET |
минус |
N=1 |
BVC MET |
нет переполнения |
V=0 |
BVS MET |
есть переполнение |
V=1 |
BCC MET |
нет переноса |
C=0 |
BCS MET |
есть перенос |
C=1 |
...с учетом знака... |
||
BGT MET |
больше нуля |
N=V и Z=0 |
BLT MET |
меньше нуля |
N xor V=1 |
BGE MET |
больше или равно нулю |
N=V |
BLE MET |
меньше или равно нулю |
N xor V=1 или Z=1 |
BGT MET |
больше нуля |
N=V и Z=0 |
BLT MET |
меньше нуля |
N xor V=1 |
BGE MET |
больше или равно нулю |
N=V |
BLE MET |
меньше или равно нулю |
N xor V=1 или Z=1 |
...без учета знака |
||
BHI MET |
больше нуля |
C=0 и Z=0 |
BLO MET |
меньше нуля |
C=1 |
BHIS MET |
больше или равно нулю |
C=0 |
BLOS MET |
меньше или равно нулю |
C=1 или Z=1 |
Первые восемь операторов в особых пояснениях не нуждаются, они проверяют отдельные биты условий. Операторы же BLO и BHIS, полностью совпадающие с BCS и BCC соответственно, введены исключительно для удобства программистов (дизассемблер обычно восстанавливает их только в виде BCS и BCC, не учитывая, в какой форме они были записаны в исходном тексте). А вот вопрос о различии в действии операторов переходов с учётом и без учёта знака нуждается в отдельном рассмотрении.
Как известно, числа со знаком изображаются в системе команд нашей ЭВМ в дополнительном коде, т.е. старший бит числа является знаковым ноль, если число положительное, и единица, если отрицательное. Диапазон абсолютных величин чисел со знаком, естественно, меньше за счёт уменьшения разрядности на 1. Поэтому существует и второе представление - без знака, только для положительных чисел, но с большей разрядностью. В связи с этим и возникают парадоксы при сравнении чисел. Например, любое число без знака больше нуля. А со знаком - больше нуля только положительные числа, отрицательные меньше. Это утверждение кажется банальным, но при использовании операторов ветвления форму представления чисел обязательно надо учитывать. Как правило, при логических операциях (сравнение кодов, сортировка и т.п.) удобнее рассматривать числа без знака, а при арифметических вычислениях - со знаком. Вообще же правильный выбор команд ветвления -довольно сложная задача, и некоторые авторы не случайно рекомендуют сводить сравнение чисел к таким вариантам, когда становится возможным применение лишь двух операторов - BNE и BEQ, которые всегда действуют однозначно. Относясь к таким рекомендациям сочувственно, нужно всё-таки ясно отдавать себе отчёт, что далеко не все задачи сводимы к частным вариантам, а плата за такое преобразование велика - потеря памяти и быстродействия. Поэтому следует применять все операторы ветвления, но тогда, когда это необходимо. А чтобы не делать ошибок, нужно точно представлять себе, какой результат возможен при той или иной операции, при тех или иных исходных данных, а не применять операторы бездумно, по шаблону.
Пример простейшей программы с использованием операторов ветвления:
; "мигание" экрана 0: MOV #177777,R0 ; В R0 все биты единичные 1: MOV #40000,R1 ; Начало экрана 2: MOV R0,(R1)+ ; Запись очередного слова в ОЗУ CMP #100000,R1 ; Конец экрана? BNE 2 ; Нет - продолжать TST R0 ; R0 = 0 (цикл очистки)? BEQ 0 ; Да - записать единицы в R0 CLR R0 ; Иначе обнулить R0 и войти BR 1 ; в цикл очистки экрана END
- JMP N - безусловный переход. Эта команда, в отличие от безусловного ветвления BR, передаёт управление по любому адресу, а не только на 256д байт, и допускает задание адреса перехода любым способом (есть, конечно, и исключения. Например, невозможна передача управления на регистр - JMP RN). Локальные метки в составе адреса перехода не допускаются.
- JSR RN,N - переход к подпрограмме. По этой команде процессор прерывает обработку текущей последовательности операторов, записывает в стек содержимое указанного регистра RN (как бы автоматически выполняя команду MOV RN,-(SP) ), переписывает в регистр RN содержимое PC (а там в этот момент записан адрес следующей команды программы), а затем заносит в PC адрес подпрограммы N (способ его задания может быть любым, с теми же ограничениями, что и для оператора JMP), вследствие чего следующей исполняется первая команда подпрограммы.
В конце подпрограммы должна стоять команда возврата - RTS RN, где RN - тот же самый регистр, что в операторе JSR. По этой команде ЦП переписывает содержимое RN в PC (т.е. восстанавливает адрес следующей за переходом к подпрограмме команды основной программы), затем восстанавливает из стека содержимое RN (как бы автоматически выполняя команду MOV (SP)+,RN), и выполнение основной программы продолжается. Из одной подпрограммы можно обратиться к другой, из неё - к третьей и т.д. Важно лишь, чтобы используемый в качестве буфера адреса возврата регистр (это может быть как один и тот же регистр для всех вложенных подпрограмм, так и разные) во время работы подпрограммы не менял своё содержимое (если, конечно, это не делается со специальными целями), иначе правильный возврат из подпрограммы станет невозможен. Количество последовательных обращений из одной подпрограммы в другую (так называемый уровень вложенности) ограничен допустимой глубиной стека.
Наиболее удобно использовать в качестве RN счётчик команд PC. Если при этом проанализировать работу операторов JSR и RTS, то окажется, что по команде JSR PC,N в стеке сохраняется, а по команде RTS PC восстанавливается непосредственно адрес возврата из подпрограммы! Этот способ обращения к подпрограммам очень удобен тем, что позволяет не занимать ни одного операционного регистра под адрес возврата, ведь их и так не очень много. Понятно, что данный способ обращения к подпрограммам используется чаще других, и для него в ассемблере «МИКРО.10К» даже предусмотрена специальная форма записи: вместо JSR PC,N - CALL N, вместо RTS PC - RET. Наличие такой формы отнюдь не запрещает в «МИКРО.10К» и стандартного обращения, просто такое - короче и удобнее.
Входов в подпрограмму (как и выходов) может быть несколько, в зависимости от разных условий, но всегда нужно помнить, что при входе в подпрограмму указатель стека SP уменьшается на 2 и при выходе из неё не оператором RTS (RET), а иначе (например BR, JMP и т.п.) стек необходимо восстанавливать. В противном случае он рано или поздно будет исчерпан и ЭВМ перестанет работать. В самой же подпрограмме со стеком следует работать осторожно и заботиться, чтобы при выходе из неё адрес возврата находился в вершине стека.
- RTS RN - возврат из подпрограммы. Подробно рассмотрен выше. В «МИКРО.10К» вместо RTS PC может использоваться оператор RET.
- MARK X - пометить стек. Эта команда применяется крайне редко, может быть, потому что ни в одном литературном источнике она не описана достаточно понятно. Попытаемся восполнить этот пробел.
Бывают случаи, когда при обращении к подпрограмме нужно передать ей ряд чисел - параметров. Эти числа при каждом обращении к одной и той же подпрограмме могут быть различными, и их может быть разное количество. В таких случаях довольно удобно перед вызовом заносить параметры в стек, извлечь же их в подпрограмме можно, например, с помощью индексной адресации или иным путём. Но так как параметров может быть разное количество, то каждый раз по выходе из подпрограммы стек надо восстанавливать. Команда MARK делает это автоматически. Как её применять?
Сначала выберем один из регистров RN под буфер адреса возврата и запишем его содержимое в стек. Какой регистр выбрать? В отличие от общего случая применения команды JSR RN, при использовании команды MARK в качестве регистра возврата может быть использован только R5 - это заложено в ЦП аппаратно. Затем занесём в стек X параметров. После этого запишем в стек саму команду в форме MARK X, где X - количество параметров (восьмеричное число от 0 до 77). Наконец, запишем адрес вершины стека в R5 и обратимся к подпрограмме через другой регистр. Если в конце подпрограммы стоит команда RTS R5, то стек и регистры автоматически будут восстановлены. Не вдаваясь в дальнейшие подробности, просто приведём пример. Регистром возврата выбран R5 (напомним, что только он и может быть выбран!), а обращение к подпрограмме пусть производится через регистр PC. Занесём в стек три параметра:
MOV R5,-(SP) ; Сохранить R5 MOV #101,-(SP) ; Занести параметры - MOV #102,-(SP) ; коды символов MOV #103,-(SP) ; "А". "В" и "С" MOV MR3,-(SP) ; Занести код команды MARK 3 MOV SP,R5 ; Занести адрес возврата CALL SBR ; Перейти к подпрограмме SBR ;......... ; Дальнейший текст программы SBR: ;......... ; Текст подпрограммы SBR RTS R5 ; Выход из подпрограммы ; с восстановлением стека MR3: MARK 3 ; Код команды MARK 3
Читатель, программирующий на ассемблере, может, конечно, предложить массу других способов как для передачи параметров подпрограмме, так и для восстановления стека без команды MARK. Но поскольку данная команда существует, разобрать её было необходимо хотя бы для того, чтобы избежать вопросов. А применять её или нет - дело сугубо личное. Большинство программирующих на ассемблере решают этот вопрос отрицательно и, может быть, правильно делают - при программировании и так хватает забот, даже без использования столь сложной по структуре и малоэффективной команды.
- SOB RN,MET - вычитание единицы и ветвление. Этот оператор может быть использован для организации циклов. При его исполнении сначала происходит вычитание единицы из содержимого регистра RN, а затем, если результат не нулевой, - переход к метке MET. Если же содержимое RN стало равно нулю, то программа выполняется дальше. Метка MET может быть как локальной, так и обычной и должна быть расположена до оператора SOB, т.е. передача управления возможна только «назад» и не более чем на 64д слова. Это объясняется тем, что в операторе SOB для хранения смещения используются только младшие 6 бит, а при исполнении перехода смещение (без знака) умножается на 2 и вычитается из PC. Циклы могут быть и вложенными, но число вложений, если не принимать специальных мер, не может превышать количества операционных регистров, которые используются для хранения цикловых переменных. Помимо организации вычислений этот оператор часто используется для создания временных задержек при работе программ, причём «пустой» цикл SOB при исходном содержимом регистра, равном нулю, выполняется на БК-0010 примерно за 0.4 с. Если потребуются большие задержки, следует прибегнуть к вложенным циклам или ввести в цикл «пустые» команды, например CMP RN,RN. Приведём пример использования данного оператора, причём это будет уже знакомая программа «мигания» экрана, но теперь решённая иными средствами.
; "мигание" экрана 0: MOV #177777,R0 ; Все биты в R0 - единицы 1: MOV #40000,R1 ; Начало экрана MOV #20000,R2 ; Цикл из 20000 повторов 2: MOV R0,(R1)+ ; Запись очередного слова в ОЗУ SOB R2,2 ; Если не конец - продолжать BEQ 0 ; Если 0, записать все единицы CLR R0 ; Иначе обнулить R0 и войти BR 1 ; в цикл очистки экрана END
Сравните эту программу с аналогичной, приведённой ранее (где цикл был организован с помощью оператора CMP). Какая из них короче и какая работает быстрее? Обратите внимание, что оператор SOB не меняет биты условий, поэтому нам удалось обойтись без проверки содержимого R0 оператором TST.
При работе с циклами нужно быть внимательным, чтобы не передать управление на оператор, задающий начальное значение переменной цикла, иначе цикл станет бесконечным. С этой же целью нужно заботиться о сохранении в цикле содержимого регистра- счётчика RN, если он должен использоваться для других целей.
3. Операторы прерывания
Эта важная группа операторов имеет общую черту: встретив такой оператор, ЦП прерывает выполнение текущей задачи. Что такое прерывание, было уже достаточно подробно описано в разделе, посвящённом архитектуре ЭВМ (см. № 2 за 1994 г.), и возвращаться к этому вопросу мы не будем. Подробно разберём только командные прерывания.
- EMT X - командное прерывание по вектору 30.
- TRAP X - командное прерывание по вектору 34.
Командные прерывания EMT и TRAP (иначе называемые командными запросами) очень похожи друг на друга. Знак X здесь - аргумент EMT или TRAP, лежащий в пределах 0...377. Рассмотрим, как работают эти команды, на примере EMT.
Пусть процессор встретил в программе команду с кодом EMT X. Первым делом происходит прерывание по вектору 30 (как обычно сохраняя в стеке SP и PC). И вот тут-то появляется «неожиданность» - по адресу, указанному в ячейке @#30, находится не конкретная, единственная программа обработки прерывания, а целый пакет программ! Но ничего ужасного не происходит: «на пороге» нас встречает маленькая, но очень важная программа - EMT-диспетчер. Что он делает? Первым делом выделяет младший байт команды EMT, т.е. её аргумент. По аргументу (или, как ещё говорят, по номеру EMT) EMT-диспетчер находит в специальной таблице адрес программы, которая нам нужна, и передаёт ей управление. Таким образом, задавая номер EMT, мы можем с помощью этой единственной команды обращаться к множеству программ, выполняющих самые разные функции.
Но чем же такое обращение отличается от вызова подпрограмм, ведь их тоже можно написать множество и обращаться к каждой просто по имени метки? Это вроде бы проще и понятнее. Верно, только вот «закавыка» - команда обращения к подпрограмме занимает в памяти два слова, а EMT - только одно! Таких обращений в тексте программы может быть несколько сотен, и тогда EMT дает экономию памяти в сотни слов. Для БК с её, мягко говоря, не слишком ёмкой памятью, это немаловажно.
А что же TRAP, какова роль этой команды? Практической разницы между EMT и TRAP нет, вернее, разница чисто условная. Для команды TRAP выполняется прерывание по вектору 34. В самой же команде указывается, как и в EMT, номер в диапазоне 0...377. Команды EMT принято использовать в системных программах ЭВМ. a TRAP - в пользовательских. Что же касается порядка их обработки, он одинаков - для вектора 34 программа обработки должна начинаться с TRAP-диспетчера, аналогичного EMT- диспетчеру.
Программа EMT-диспетчера БК-0010 расположена по адресу 100112 и обслуживает множество команд EMT с различными номерами. Каждая из них делает что-то своё, иногда эти действия весьма сложны, причём ими может воспользоваться не только сама ЭВМ, но и программист. Тем самым иногда можно избежать написания длинных и очень сложных подпрограмм - достаточно вписать в программу команду EMT с нужным номером, а подпрограмма обработки уже готова - она «ждёт» в ПЗУ. Тут необходимо отметить (спасибо разработчикам БК-0010, а особая благодарность - автору МДС М.И. Дябину), что содержимое мониторно-драйверной системы нашей ЭВМ (а тем самым и система командных запросов EMT) осталось неизменным с 1985 г., когда компьютер БК-0010 «пошёл в серию», и до сего дня, когда освоен выпуск последней модели БК-0010.01 с новой отличной бездребезговой клавиатурой и полностью переработанной печатной платой. Это очень важно - ведь изменись МДС хоть частично, все ранее разработанные Прикладные программы оказались бы написанными впустую, они не работали бы на новых моделях компьютеров! К чему это приводит, видно на примере БК-0011. Не успела она появиться в продаже, как разработчикам что-то в ней не понравилось, и они сочли своим долгом изменить её мониторно-драйверную систему. А новая МДС БК-0011М оказалась программно несовместимой с прежней. Это, без всякого преувеличения, настоящая катастрофа для всех владельцев БК-0011 - старый компьютер уже не выпускают, программ для него нет и никогда не будет - слишком мало экземпляров БК-0011 было выпущено! Это трагедия и для разработчиков - они сразу и навсегда подорвали доверие пользователей к новой машине: кто гарантирует, что завтра снова не изменится МДС БК-0011М и её владельцы вновь не окажутся «у разбитого корыта»... Это удар и для программистов - всё, разработанное ими в ужасной спешке и зачастую почти бесплатно для БК-0011, уже не годится для БК-0011М и никогда не окупится.
К счастью, с БК-0010 ничего такого не происходило - все её модели до настоящего времени полностью программно совместимы друг с другом на уровне машинных команд, в том числе и EMT-запросов.
На что же годны имеющиеся в системе БК-0010 командные прерывания EMT, или, другими словами, что они делают?
Начнём с того, что на БК-0010 для номеров EMT приняты только чётные числа. Это чистая условность - можно было бы использовать и нечётные, но с чётными EMT-диспетчер вышел чуть проще, а такого множества функций, какое было бы возможно с использованием всех чисел, просто не нужно.
Каждая команда EMT (имеющая свой номер) делает что-то своё. Но между ними есть и нечто общее - обычно они требуют задания каких-либо параметров (исходных данных). Данные эти задаются, как правило, в одном или нескольких регистрах общего назначения, до вызова команды EMT. После выполнения команды содержимое РОН может остаться тем же (сохраняться) или стать иным. Часто новое значение РОН несёт ценную информацию, которая может быть использована (а иногда эта информация - единственный результат выполнения EMT). По ходу знакомства с различными EMT-функциями мы будем приводить сведения как о входной, так и о выходной информации (если она представляет интерес), и указывать, какие РОН не сохраняются при выполнении EMT (о их сохранении следует позаботиться до вызова EMT). Наиболее употребительным функциям EMT мы будем уделять больше внимания. Будем рассматривать их не в порядке номеров, а по функциональному назначению.
- EMT 4 - инициализация драйвера клавиатуры Переписываются (устанавливаются в исходные значения) векторы прерывания от клавиатуры (60 и 274), сбрасывается маска прерываний от клавиатуры (разряд 06 регистра 177660), устанавливается режим передачи кодов клавиатуры по запросам рабочей программы и код 12 для клавиши «ВВОД». R0 не сохраняется. EMT 4 используется весьма редко, например если по ходу программы мы запретили прерывание от клавиатуры и нужно его разрешить.
- EMT 14 - инициализация всех драйверов. Применяется, напротив, очень часто для «начальной установки» всех параметров ЭВМ (а особенно режимов дисплея). Обеспечивает установку в исходное состояние всех ячеек системной области (кроме стека), всех векторов прерывания и системных регистров, очистку экрана, установку исходных режимов дисплея и ТЛГ-канала, очистку порта ввода-вывода. Таким образом, БК-0010 приводится практически в такое же состояние, как после перезапуска системы (или включения питания). Регистры, кроме R5, не сохраняются. Эту команду следует применять с некоторой осторожностью, так как из-за сохранения содержимого стека после выполнения EMT 14 иногда по окончании работы программы возможен неожиданный эффект - выход из МСД в ПМ или даже в Фокал со стиранием ОЗУ.
- EMT 6 - ввод кода символа с клавиатуры. По данной команде работа программы прерывается, и ЭВМ ждёт нажатия клавиши. Код нажатой клавиши заносится в младший байт R0, старший байт очищается. Применяется очень широко как для ввода кодов отдельных символов или команд, так и просто для приостанова программы и ожидания нажатия клавиши (организация паузы). Следует помнить, что. когда ЭВМ ждёт нажатия клавиши по команде EMT 6, она находится в режиме прерывания, следовательно, вершина стека уже на 4 меньше исходной. Нажатие в этот момент клавиши «СТОП» - не одно и то же, что останов с её помощью работающей программы, в смысле содержимого стека на момент останова и положения его вершины (содержимого SP), иногда это может быть существенно.
- EMT 16 - передача кодов драйверу дисплея. Это «EMT 6 наоборот» - вывод на экран символа, код которого содержится в младшем байте R0 (или исполнение команды управления режимами экрана, если в R0 её код). Содержимое старшего байта R0 при исполнении EMT 16 значения не имеет и не меняется. Пожалуй, это наиболее часто применяемая команда EMT - с её помощью можно выводить как отдельные символы, так и целые тексты. Все регистры при её исполнении сохраняются. Приведём примеры использования этих команд.
1) MOVB #77,R0 ; Вывод на экран EMT 16 ; символа "?" END 2) MOVB #14,R0 ; Сброс экрана EMT 16 ; и вывод символа MOVB 'A',R0 ; "А" EMT 16 END 3) MOV 'АБ',R0 ; Вывод на экран EMT 16 ; Двух символов SWAB R0 ; "А" и "Б" EMT 16 END 4) 0: EMT 6 ; Вывод на экран EMT 16 ; символов, вводимых CMPB #40,R0 ; с клавиатуры; завершение - BNE 0 ; по клавише "ПРОБЕЛ" END 5) MOV #7,R0 ; Цикл MOV #100,R1 ; из 64д звуковых 0: EMT 16 ; сигналов SOB R1,0 END 6) 0: EMT 6 ; Пауза до нажатия клавиши MOV #TEX,R1 ; Адрес начала текста MOVB #14,(R1)+ ; Команда очистки экрана MOVB 'П',(R1)+ ; Ввод в ОЗУ MOV 'ри',(R1)+ ; строки символов - MOV 'вe',(R1)+ ; слова "Привет" MOVB 'т',(R1)+ CLRB (R1)+ ; Нулевой байт - конец текста MOV #TEX,R1 ; Адрес начала текста 1: MOVB (R1)+,R0 ; Вывод текста на экран BEQ 0 ; побайтно до нулевого байта, EMT 16 ; затем - повторение с начала BR 1 ТЕХ: END
В приведённых примерах листинги заканчиваются командой END, но это не обязательно - они могут представлять собой подпрограммы или фрагменты программ. Для полного использования возможностей команды EMT 16 необходимо знать не только коды символов БК-0010, но и командные коды. Эти сведения достаточно полно изложены в прилагаемых к БК руководствах, и подробно останавливаться на них нет необходимости.
Заметим, что приведённая в последнем примере строка текста предварительно записывается в память отнюдь не оптимальным способом, но другого варианта мы пока ещё «не проходили».
Контрольные вопросы и задания
- Чему будет равно содержимое регистра R0
в результате исполнения последовательно каждой из команд:
MOV #77000,R0 INC R0 COMB R0
-
77000; 77001; 77376.
-
- Чему будет равно содержимое регистра R5
в результате исполнения последовательно каждой из команд:
MOV #177777,R5 ASL R5 ASL R5 ROL R5 ROL R5
-
177777, 177776; 177774; 177771, 177763.
-
- Что получится в результате выполнения команды
XOR #1000,@#2400
?
-
Ничего, так как первым операндом команды XOR может быть только RN - один из регистров общего назначения. Такая команда даже не будет оттранслирована ассемблером.
-
- Как переписать младший байт из регистра R0 в регистр R4, не изменяя при этом старшие байты R0 и R4 (остальные регистры и ячейки памяти не использовать)? Напишите соответствующую программу.
- Как установятся биты условий ССП N,
Z, V,
С в результате исполнения последовательно
из каждой команд:
MOV #177777,R0 INC R0 ADD #77777,R0 ASL R0 ASL R0
-
N=1; Z=0; V=0; C=0
N=0; Z=1; V=0; C=0
N=0; Z=0, V=0; C=0
N=1; Z=0; V=1; C=0
N=1; Z=0; V=0; C=1
-
- Будет ли выполняться ветвление по оператору BGE
в данной программе:
MOV #377,R0 MOV #120,R1 CMPB R0,R1 BGE MET
Что изменится, если вместо оператора CMPB использовать CMP?
-
Не будет, так как число 377 отрицательное и меньше числа 120, а оператор BGE учитывает знак. При замене CMPB на CMP ветвление будет выполняться
-
- Сколько раз будет выполнен цикл SOB в
данном случае:
MOV #10,R0 0: DEC R0 SOB R0,0
-
4 раза, так как регистр-счётчик R0 дополнительно модифицируется в геле цикла.
-
- Сколько символов «А» выведет на
экран следующая программа:
MOV 'A',R0 0: MOV #10,R1 EMT 16 SOB R1,0
-
Бесконечное множество, так как цикл организован неправильно - запись константы в R1 включена в цикл.
-
- Напишите программу, которая по нажатию любой клавиши (кроме клавиши «СТОП») выводила бы на экран символ «?»
- Напишите программу, которая преобразовывала бы символы, вводимые
с клавиатуры в любом регистре («РУС», «ЛАТ», «СТР», «ЗАГЛ»),
в символы в регистре «ЛАТ-ЗАГЛ» соответственно нажатой клавише
(например, «л» - в «L») и выводила
на экран исходный символ, а через чёрточку - преобразованный, причём
каждая следующая пара символов должна выводиться с новой строки.
-
Подсказка: для того чтобы преобразовать символы из одного регистра в другой, достаточно сбросить (или установить) определённые разряды (биты) кода символа. Определить, какие именно, легко, если выписать двоичные коды символов разных регистров друг под другом.
0: MOV #12,R0 EMT 16 EMT 6 EMT 16 MOV R0,R1 MOV '-',R0 EMT 16 MOV R1,R0 BIC #240,R0 EMT 16 BR 0 END
-
CLRB R4 BISB R0,R4
0: EMT 6 MOV '?',R0 EMT 16 BR 0
В этой части мы закончим рассмотрение EMT-функций БК-0010 и их роли в программировании на ассемблере.
- EMT 10 - ввод строки символов с клавиатуры. Данная команда позволяет вводить с клавиатуры в заданную область ОЗУ последовательность кодов символов (а также командные коды). Адрес начала области ОЗУ, в которую осуществляется ввод, задаётся в R1, ограничители строки - в R2: младший байт R2 - длина строки в байтах (нуль соответствует 200000 байт), старший - код символа-ограничителя. Работа EMT 10 заканчивается после удовлетворения одного из двух условий: строка достигает заданной длины или вводится символ-ограничитель. причём его код заносится в строку последним. По окончании ввода в R1 возвращается адрес байта, следующего за концом строки, в R2 - разность заданной длины и реально введённого количества символов. Ошибочно введённые знаки можно удалять клавишей «ЗАБОЙ». EMT 10 широко применяется для ввода текстов, чисел в символьном виде для последующей их обработки, имён файлов и т.п.
- EMT 20 - вывод строки символов на экран. Эта команда выполняет действие, противоположное EMT 10. Адрес начала строки символов задаётся в R1. ограничители строки - в R2 (так же, как для EMT 10. младший байт - длина строки, 0 соответствует 200000, старший - код символа-ограничителя, причём он выводится на экран последним). С помощью EMT 20 можно не только выводить символы, но и изменять режимы вывода (очистка экрана, переключение формата 32/64 символа в строке и т.п.), если коды соответствующих команд присутствуют в строке. Применяется очень широко для вывода текстовых сообщений. Примеры:
1) 0: EMT 6 ; Пауза до нажатия клавиши MOV #TEX,R1 ; Адрес начала текста MOVB #14,(R1)+ ; Команда очистки экрана MOVB 'П',(R1)+ ; Запись в ОЗУ MOV 'ри',(R1)+ ; строки символов - MOV 'ве',(R1)+ ; слова "Привет" MOVB 'т',(R1)+ CLRB (R1)+ ; Нулевой байт - конец текста MOV #TEX,R1 ; Вывод на экран строки текста, CLR R2 ; с метки ТЕХ EMT 20 ; до нулевого байта BR 0 ; Программа зациклена ТЕХ: END 2) 0: MOV #TEX,R1 ; Ввод строки текста (максимальная MOV #5377,R2 ; длина 256д байт, символ- EMT 10 ; ограничитель - код 12 "ВВОД") MOV #TEX,R1 ; и её последующая выдача на MOV #5377,R2 ; экран. Буфер строки начинается EMT 20 ; с метки ТЕХ BR 0 ; Программа зациклена ТЕХ: END
- EMT 22 - запись символа в служебную строку. Позволяет вывести в служебную строку необходимые сообщения, например фамилию автора, название программы и т.п. Информация выводится по одному символу (соответственно, для вывода строки нужно использовать цикл), код этого символа записывается в R0, а номер позиции в служебной строке - в R1. Символы выводятся как в режиме БЛР- ИСУ, т.е. коды управления (10, 22, 30 и т.п.) отображаются в виде стрелок. Нулевое содержимое R0 означает очистку служебной строки. Позиция отсчитывается от левого края, первому символу соответствует 0. При использовании команды необходимо учитывать, что в правой части служебной строки ЭВМ сама выводит сообщения при переключении режимов, при этом текст, выведенный туда пользователем, может быть затёрт. Пример:
0: MOV #2,R1 ; Вывод начиная с 3-й позиции EMT 6 ; Пауза до нажатия клавиши MOV #TEX,R2 ; Адрес начала текста MOV #233,R0 ; Команда переключения EMT 16 ; формата CLR R0 ; Очистка EMT 22 ; служебной строки MOV 'Пр',(R2)+ ; Запись в ОЗУ MOV 'ив',(R2)+ ; строки символов – MOV 'eт',(R2)+ ; слова "Привет!" MOV '! ',(R2)+ CLRB (R2)+ ; Нулевой байт - конец текста MOV #TEX,R2 ; Адрес начала текста 1: MOVB (R2)+,R0 ; Вывод текста BEQ 0 ; в служебную строку EMT 22 INC R1 ; Программа зациклена BR 1 ТЕХ: END
- EMT 24 - установка курсора по координатам. Координата X задаётся в регистре R1, Y - в R2. В графическом режиме по EMT 24 аналогичным образом устанавливается графический курсор. Если значение координат превышает число знакомест (или точек) экрана, то курсор устанавливается по модулю имеющегося на экране числа знакомест. (В данном случае слова «по модулю» обозначают использование функции MOD, т.е. вычисление остатка от деления. Так, если значение X задано равным 79. а ширина экрана в данном режиме вывода соответствует 64 символам, то курсор устанавливается в позиции, равной 79 MOD 64 = 15. - Прим. ред.) Началом координат, как это принято для БК-0010, считается левый верхний угол экрана.
- EMT 26 - чтение координат курсора. Как и предыдущая команда, EMT 26 работает и в символьном, и в графическом режимах. Значение координат читается: X - в R1, Y - в R2.
- EMT 30 - формирование точки по координатам. Координаты точки задаются: X - в R1, Y - в R2. В R0 указывается код операции: 1 (или любое ненулевое значение) - запись точки, 0 - стирание. Предварительная установка графического режима не требуется. Цвет (в цветном режиме) может быть задан соответствующей командой заранее. Если координаты точки выходят за пределы экрана, то команда игнорируется, но заданные координаты запоминаются как последние отработанные.
- EMT 32 - формирование вектора. Задаются координаты конца вектора: X - в R1, Y - в R2. В R0 указывается код операции, как и для EMT 30: 1 (или любое ненулевое значение) - запись вектора, 0 - стирание. Может быть предварительно задан цвет. Началом вектора считается конец последнего начерченного или стёртого вектора (по EMT 32) либо последняя обработанная точка (по EMT 30). Если координаты начала или конца вектора оказываются за пределами экрана, вектор в соответствии с ними всё равно строится. хотя на экране отображается только его видимая часть. Приведём пример.
; "муаровый" узор MOV #1,R0 ; Зачерчивание экрана CLR R3 ; из левого верхнего 0: CLR R1 ; угла фигурой типа CLR R2 ; "радиальный луч" EMT 30 ; Регистр R3 используется MOV R3,R1 ; как буфер координаты MOV #357,R2 ; X конца луча EMT 32 CMP (R3)+,(R3)+ BR 0 ; Программа зациклена
Отметим, что предпоследняя команда ничего, в общем-то, не сравнивает, а использована только для увеличения содержимого R3 сразу на 4. При этом она занимает всего одно слово, в отличие, например, от команды ADD #4,R3, занимающей два слова. С таким использованием команды CMP мы уже знакомы.
- EMT 12 - установка ключей клавиатуры. Как известно, БК-0010 имеет механизм ключей (или функциональных клавиш) – клавиши 0...9 могут быть запрограммированы на выдачу определённых текстовых строк при нажатии их в регистре «НР» («АР2»). Для программирования ключа необходимо задать его номер (от 1 для клавиши «1» до 12 для «0») в регистре R0, а адрес текста ключа в памяти - в R1 и выполнить EMT 12 Первый байт текста должен содержать длину строки в байтах и как символ не выводится. При содержимом R1, равном 0, происходит сброс ключа. Если программирование ключей производится для языка высокого уровня, например Фокала, то тексты ключей необходимо размещать в специально отведённой для этого зоне (для Фокала - по адресам 1000...1377). R0 не сохраняется. Пример (программирование ключа 5):
MOV #TEX,R1 ; Адрес начала текста MOVB #7,(R1)+ ; Числосимволов текста MOVB 'К',(R1)+ ; Текст MOV 'EY',(R1)+ ; "KEY N 5" MOV ' N',(R1)+ MOV ' 5',(R1)+ MOV #5,R0 ; Номер ключа MOV #TEX,R1 ; Адрес текста ключа EMT 12 ; Запрограммировать ключ 0: BR 0 ; Ожидание (вечный пустой цикл) ТЕХ: END
После запуска данной программы ключ будет запрограммирован. Если нажать клавишу «СТОП», можно выйти в ПМ или МСД и проверить результат, нажимая клавишу «5» в регистре «АР2». (Отметим, что ожидание с помощью BR 0 в конце программы нам понадобилось только потому, что мы ещё не успели познакомиться с командой останова процессора.)
- EMT 34 - чтение слова состояния дисплея (ССД, не пугать с ССП!). ССД - это двухбайтное машинное слово, дающее исчерпывающее представление об установленных режимах дисплея БК-0010. По EMT 34 ССД заносится в R0, причём каждому режиму дисплея соответствует один из битов: если он равен 1, то режим включён, если 0 - выключен (или включён альтернативный режим). В системной области ОЗУ начиная с адреса 40 размещён массив служебных переменных - признаков режимов, с которых и считывается при отработке EMT 34 слово состояния дисплея. При этом каждому биту ССД соответствует байт в памяти, его содержимое равно 0 при бите ССД, равном 0, и 377 - при 1 (кроме бита 03, сигнализирующего о включении русского регистра, которому соответствует содержимое ячейки 43, равное 200), как это указано в таблице ССД.
Разряд ССД |
Адрес байта |
Включённый режим дисплея |
---|---|---|
00 |
40 |
Цвет (режим 32 символа в строке) |
01 |
41 |
Инверсия экрана |
02 |
42 |
Режим «РП» (расширенной памяти) |
03 |
43 |
Регистр «РУС» |
04 |
44 |
Подчёркивание |
05 |
45 |
Инверсия символа |
06 |
46 |
Индикация символов управления |
07 |
47 |
Блокировка редактирован ия |
08 |
50 |
Режим «ГРАФ» |
09 |
51 |
Режим «ЗАП» |
10 |
52 |
Режим «СТИР» |
11 |
53 |
Режим 32 символа в служебной строке |
12 |
54 |
Подчёркивание в служебной строке |
13 |
55 |
Инверсия символа в служебной строке |
14 |
56 |
Гашение курсора |
15 |
57 |
Не используется |
Очевидно, что, пользуясь ССД (или прямо проверяя значения служебных байтов - переменных), можно установить, какие режимы дисплея включены, и программно переключать нужные режимы как в кодах (выдавая необходимые команды с помощью EMT 16), так и на языках высокого уровня.
- EMT 36 - работа с магнитофоном. Это самое сложное из всех командных прерываний БК-0010. Перед обращением к нему в R1 заносится адрес блока параметров. Блок параметров - это специально выделенная зона ОЗУ, куда предварительно записывается информация, обеспечивающая работу EMT 36. Блок параметров может быть размещён начиная с любого чётного адреса ОЗУ, но в системной области под него специально зарезервирована зона адресов 320...371. Приведём распределение ячеек этой зоны (при расположении блока параметров в любом другом месте ОЗУ последовательность ячеек сохраняется):
Адреса байтов |
Содержимое |
---|---|
320 |
Команда |
321 |
Ответ |
322, 323 |
Адрес массива на запись или чтение |
324, 325 |
Длина массива на запись (при чтении нужно обнулять) |
326...345 |
Имя массива на запись или чтение |
346, 347 |
Адрес текущего массива |
350, 351 |
Длина текущего массива |
352...371 |
Имя текущего массива |
(Текущим называется массив (файл), имя которого прочитано последним, независимо от того, загружался ли он в ОЗУ.) Вот по этим-то адресам и заносятся все необходимые данные при чтении или записи файлов в Фокале, Бейсике, ПМ или МСД.
Байт «команда» в блоке параметров задаёт необходимый режим работы ЭВМ с магнитофоном и может принимать значения:
0 - останов двигателя магнитофона,
1 - пуск двигателя,
2 - запись файла на МЛ,
3 - чтение с МЛ.
4 - фиктивное чтение.
Отметим, что при исполнении команд 2, 3 и 4 двигатель магнитофона включается и отключается автоматически, отдельная подача команд 0 и 1 не требуется. (Вообще говоря, команды 0 и 1 нужны только при оборудовании магнитофона дистанционным управлением. В этом случае, когда после завершения операций 2, 3 или 4 БК отключает двигатель, невозможно даже просто перемотать ленту - дистанционное управление блокирует запуск двигателя с клавиш магнитофона. Для ручной перемотки нужно либо отключить ДУ, отстыковав соответствующий штекер магнитофонного кабеля БК, либо, что проще, заранее предусмотреть в программе команды включения-выключения двигателя аналогично реализации МП и МС в МСТД. - Прим. ред.)
Байт ответа сообщает о результатах исполнения команды и может принимать следующие значения:
0 - операция завершена без ошибок,
1 - имя файла на МЛ не совпадает с заданным,
2 - ошибка контрольной суммы,
4 - останов по клавише «СТОП».
Теперь, наверное, вам понятно, как записать или прочитать заданный файл. Рассмотрим пример (выбран вариант записи файла на МЛ, как более сложный):
; Запись файла на МЛ с помощью EMT 36 BEG: MOV #346,R3 ; Конец имени файла 0: MOVB #40,-(R3) ; Очистка имени файла CMP #320,R3 ; (запись пробелов во все байты, BNE 0 ; до R3 = 320) MOV #2,(R3)+ ; Команда "запись"; R3+2 = 322 MOV 'A=',R0 ; Запрос "адреса" CALL INP ; Ввод адреса; R3+2 = 324 MOV 'Д=',R0 ; Запрос "ДЛИНА=" CALL INP ; Ввод длины; R3+2 = 326 MOV 'И=',R0 ; Запрос "ИМЯ=" EMT 16 ; Выдача запроса SWAB R0 ; на экран EMT 16 MOV #5020,R2 ; Ограничители; "ВВОД" или длина 16д MOV R3,R1 ; Адрес имени - 326 EMT 10 ; Ввод имени MOVB #40,-(R1) ; Заменить последний байт имени ; (код 12) на пробел MOV #320,R1 ; Адрес блока параметров EMT 36 ; Выполнить запись файла EMT 6 ; Ожидание нажатия клавиши BR BEG ; Программа зациклена ; Выдача запросов, ввод числовых данных INP: EMT 16 ; Первый символ запроса SWAB R0 EMT 16 ; Второй символ CALL @#100472 ; Ввод восьмеричного числа в R5 MOV R5,(R3)+ ; Число - в блок параметров; R3+2 MOV #12,R0 ; Перевод EMT 16 ; строки RET ; Выход из подпрограммы END ; Конец программы
Построчный комментарий видимо, делает ненужными дальнейшие пояснения. Отметим только, что для ввода восьмеричного числа, задающего начальный адрес и длину, мы воспользовались подпрограммой ПЗУ, расположенной по адресу 100472, - это избавило от необходимости отводить под аналогичную, довольно длинную подпрограмму место в ОЗУ. Часть команд программы, повторяющихся дважды, вынесены в подпрограмму INP. Данный алгоритм не является единственно возможным, но автор стремился сделать программу предельно короткой. Удалось ли это - решать читателям, когда они начнут писать свои аналогичные программы. Тем и интересно программирование, что вариантов решения одной задачи, даже не слишком сложной, может быть множество. Нередко при работе с файлами требуется не ввод адреса и длины с клавиатуры, а задание этих параметров программно (например, начало и длина файла, набираемого в редакторе текстов). тогда алгоритм будет иным. Если же требуется не запись файла на МЛ, а его чтение, фиктивное чтение или управление магнитофоном, характер программы также изменится. Мы надеемся, что после приведённого примера читателю не составит труда написать эти программы самостоятельно.
Пользуясь случаем, рассмотрим кратко формат записи файла БК-0010 на МЛ. Файл начинается с установочной последовательности (УсП), которая представляет собой запись 10000 нулевых битов. Эта последовательность нулей служит, во-первых, для поиска начала файла, а во-вторых, для автоматической подстройки скорости чтения (для разных БК-0010, а особенно для разных магнитофонов она может существенно различаться; это же свойство обеспечивает возможность записи файлов с повышенной скоростью). Но ведь последовательность из 10000 нулей может присутствовать и в самом файле! Чтобы не спутать такой фрагмент с началом файла, на МЛ вслед за УсП записывается маркер - специальная кодовая последовательность импульсов. Помимо опознавания начала файла маркер служит также для определения фазы записи - ведь полярность выхода различных магнитофонов может быть разной. Если маркер опознан, начинается чтение следующего за ним оглавления файла, представляющего собой точную копию блока информации, находящейся при записи файла по адресам 322...345 (т.е. адреса, длины и имени файла), но при чтении эти параметры заносятся по адресам 346...371. После чтения оглавления производится сверка имени файла, и при его несовпадении с заданным в ячейку 301 записывается байт ответа и производится выход в основную программу. Затем, если имя совпало с заданным на чтение, производится, в зависимости от команды, истинное либо фиктивное чтение. После загрузки в ОЗУ БК считывает последнее слово файла - контрольную сумму (КС). Затем вычисляется КС загруженного в ОЗУ массива и сравнивается с прочитанной. В зависимости от результата этого сравнения формируется байт ответа в блоке параметров, и работа команды EMT 36 завершается. Запись производится аналогично. КС, сопровождающая файл на МЛ, вычисляется как сумма всех его байтов с учётом битов переноса. При вычислении КС загруженного файла его УсП, маркер, оглавление и сама КС не учитываются.
(Завершая обсуждение функции EMT 36, следует обратить внимание на её использование в БК, оснащённых дисководом. При работе с большинством имеющихся сегодня дисковых систем (кроме разве лишь давно устаревших версий НОРТОНа для БК без расширенного ОЗУ) реализуется автоматическая «переадресация» чтения- записи файлов по EMT 36 на диск. Делается это путём перехвата EMT-прерывания и подмены функции EMT 36 на её «дисковую» версию, что обеспечивает нормальную работу с диском большинства старых программ, рассчитанных на магнитофон.
Из сказанного следует два достаточно важных вывода. Во-первых, при написании прикладных программ с целью обеспечения их универсальности ввод-вывод файлов нужно производить стандартным способом через EMT 36, а не путём прямого обращения к «магнитофонным» подпрограммам монитора (кроме особых случаев, когда это действительно необходимо, а программа рассчитана только на работу с магнитной лентой). Второй вывод касается адаптации уже существующих программ к диску. Эта адаптация в большинстве случаев состоит в обеспечении загрузки с диска отдельных частей многофайловых программ. Так вот, делать это следует тоже только с использованием стандартной функции EMT 36. Попытки же писать собственные загрузчики, непосредственно работающие с каталогом имён и секторами данных на диске, приводят к тому, что подобная адаптация работает лишь в одной ОС. А в результате кому-то приходится в конце концов «взламывать» такую программу и делать адаптацию заново, дабы получить возможность работы с ней в других операционных системах. - Прим. ред.)
Теперь кратко упомянем несколько оставшихся EMT. Все они относятся к работе с ТЛГ-каналом. Поскольку эти команды применяются, когда необходимо обеспечить связь между несколькими ЭВМ, между ЭВМ и принтером с последовательным каналом и т.п., то ясно, что они - «не для простых смертных», а для довольно квалифицированных пользователей (программистов). Как уже говорилось, для использования ТЛГ-канала в некоторых моделях БК необходимо установить на плате компьютера перемычки, чтобы соединить вход и выход ТЛГ с контактами разъёма порта ввода-вывода, что также требует некоторой квалификации. Перемычки показаны на принципиальной схеме ЭВМ (см. № 2 за 1994 г., с.35, справа внизу, перемычки 52 и 53. - Прим. ред.), а мы на этом останавливаться не будем, чтобы не «провоцировать» начинающих на рискованные эксперименты, грамотный же специалист легко найдёт их сам. Как говорится, «Богу - Богово...».)
Несколько более подробные сведения о данных командах содержатся в «Руководстве системного программиста», прилагаемом к БК, а мы их просто перечислим и назовём.
- EMT 40 - инициализация драйвера тлг-канала.
- EMT 42 - передача байта по тлг- линии.
- EMT 44 - приём байта с тлг-линии.
- EMT 46 - передача массива по тлг-линии.
- EMT 50 - приём массива с тлг- линии.
В EMT-диспетчере БК-0010 предусмотрены также ещё 16 так называемых резервных входов EMT с номерами 52, 54, ... 110. При обращении к ним происходит передача управления по адресам, равным соответственно 160000, 160004, ... 160074. Эти входы могут быть использованы, если вместо тест-ПЗУ разместить, начиная с адреса 160000, ПЗУ пользователя. (При подключённом контроллере дисковода его ПЗУ попадает как раз в область адресов начиная с 160000, и тогда некоторые из названных EMT-функций можно использовать для вызова подпрограмм доступа к диску. Подробнее об этом см. в № 1 за 1993 г., с. 102. - Прим. ред.)
На этом рассмотрение командных прерываний EMT можно считать законченным. К вопросу о том, как использовать полученные знания, мы вернёмся позже, а пока продолжим изучение операторов ассемблера и первым разберём уже упомянутый ранее оператор TRAP.
- TRAP X - командное прерывание по вектору 34, где X – номер TRAP, восьмеричное число от 0 до 377. Предназначен этот оператор в основном для программ пользователя, а в остальном он очень похож на EMT. Пользователь может написать свою программу TRAP-диспетчера и создать свой пакет программ обработки прерываний TRAP, аналогичный EMT-пакету, но решающий другие задачи. Воспользуемся случаем и приведём пример простейшего TRAP-диспетчера. Предположим, что мы хотим использовать прерывание TRAP для такой банальной цели, как вывод на экран символов, коды которых соответствуют номерам TRAP. Вспомним, как выполняется командное прерывание. Обнаружив в программе команду прерывания TRAP, процессор выполняет действия, которые можно условно изобразить следующей программой:
MFPS -(SP) ; Сохранить текущее ССП MOV PC,-(SP) ; Сохранить адрес следующей ; команды MTPS @#36 ; Записать новое ССП MOV @#34,PC ; Записать адрес программы ; обработки прерывания
Конечно, такую программу нам писать не нужно, процессор делает это сам: программа обработки прерываний заложена в него на аппаратном уровне (если точнее - на так называемом микропрограммном). Но вот процессор перешёл к программе обработки прерывания. Что будет собой представлять эта программа для нашего случая? Раз мы хотим получить вывод символов, код которых соответствует номеру TRAP, нам, видимо, следует извлечь этот номер. Вспомним, что коды TRAP лежат в диапазоне 104400...104777, т.е. искомый номер - это младший байт команды. А как «выудить» командный код? В стеке у нас сохранен адрес следующей команды. Уменьшив его на 2, получим адрес TRAP. А дальше уже просто: косвенное обращение по этому адресу даст код TRAP, используем его младший байт как код символа, вот и всё! Но прежде чем всё это делать, мы должны записать в вектор 34 значение, соответствующее адресу нашей программы обработки прерывания. Пусть программа обработки TRAP помечена меткой TRP. Запишем:
MOV #TRP,@#34 ; Записать новый ; вектор 34
Если бы требовалось на время исполнения TRAP запретить все прерывания от внешних устройств, мы могли бы задать также нужный приоритет процессора во втором слове вектора, например.
MOV #340,@#36 ; Задать максимальный ; приоритет
Однако в данном случае это не принципиально. А теперь напишем саму программу обработки прерывания.
; Программа обработки прерывания TRAP TRP: MOV R0,-(SP) ; Сохранить R0 MOV 2(SP),R0 ; Извлечь адрес следующей команды MOV -(R0),R0 ; Извлечь текущую команду (TRAP) EMT 16 ; Вывести на экран символ, код ; которого в младшем байте R0 MOV (SP)+,R0 ; Восстановить R0 RTI ; Выход из прерывания
Как видим, программа получилась очень простой, это диктуется целью, которую мы себе поставили. Если теперь написать, например, последовательность команд: TRAP 101, TRAP 102, TRAP 103, то на экран будут выведены три символа: «АВС». Команды их выдачи на экран занимают всего три слова, тогда как, например, при «классическом» способе вывода (занести код символа в регистр R0 и выполнить EMT 16) потребовалось бы девять слов! Между тем, сама программа обработки TRAP занимает у нас семь слов. При достаточно частом обращении к ней мы получим существенную экономию памяти. Если же задача сохранения и последующего восстановления R0 не ставится, то программа обработки TRAP будет ещё короче.
(Может показаться, что в данном примере функция TRAP используется слишком уж «примитивно». Однако на практике необходимость в реализации подобного механизма передачи аргумента функции через номер команды TRAP встречается довольно часто. Так, в трансляторе вильнюсского Бейсика именно таким способом осуществляется обращение к подпрограмме выдачи на экран сообщений об ошибках: номер ошибки содержится в младшем байте команды TRAP. - Прим. ред.)
- IOT - командное прерывание по вектору 20. Предназначено для обслуживания устройств ввода-вывода в операционных системах, в БК-0010 практически не используется. Выполняется точно так же, как EMT или TRAP, с той разницей, что младший байт команды не содержит дополнительной информации (номера). Может быть использовано для обращения к какой-либо одной подпрограмме.
- BPT - командное прерывание по вектору 14. Используется в отладчиках, подробно будет рассмотрено позже.
- RTI - возврат из прерывания. Используется для выхода из подпрограммы обработки прерываний (кроме отладочных). Восстанавливает стек, ССП и адрес очередной команды. При отработке оператора RTI процессор автоматически выполняет (на микропрограммном уровне) действия, аналогичные последовательности команд:
MOV (SP)+,PC ; Восстановить адрес ; команды прерванной ; программы MTPS (SP)+ ; Восстановить ССП
- RTT - возврат из отладочного прерывания. Используется для выхода из отладочного прерывания, когда установлен T-разряд ССП, но может применяться и для выхода из других прерываний. Отличие RTT от RTI состоит лишь в том, что прерывание по T-разряду при отработке RTT не наступает, а выполняется следующая команда и лишь после неё происходит прерывание по T-разряду. Можно сказать, что команда RTT «непрозрачна при трассировке». (Подробно о её использовании мы поговорим позднее.)
- HALT - останов. В сущности, эта команда есть не что иное, как командное прерывание по вектору 4, и дальнейшие действия ЭВМ всецело зависят от того, как построена программа обработки прерывания по этому вектору. Команда (обычно неправильно относимая к «командам управления машиной») может быть использована не только для останова, но и для любых других целей, аналогично прочим командам прерываний, но нужно помнить, что, изменяя вектор 4 и программу обработки прерывания по этому вектору, мы меняем и порядок работы клавиши «СТОП».
Операторы управления машиной
- WAIT - ожидание. Приостанавливает выполнение текущей программы до прерывания от внешних устройств (клавиатуры, таймера, клавиши «СТОП»). Может быть использован для организации паузы и ожидания реакции пользователя (или прерывания от внешних устройств), но нужно помнить, что прерывания возникают при этом в соответствии с приоритетом внешних устройств и процессора, и для прерывания, например, от клавиатуры приоритет процессора должен быть менее 4 (Применяется редко.)
- RESET сброс внешних устройств, Оператор вызывает выдачу сигнала INIT по командной магистрали общей шины, который сбрасывает все устройства ЭВМ (порт, системные регистры) в исходное состояние. Особенность выполнения данного оператора на БК-0010 такова, что после него обычно необходимо инициализировать драйвер клавиатуры командой EMT 4. Применяется крайне редко. (Оператор RESET может быть использован и нестандартно, обеспечивая при этом возможность распознавания одновременного нажатия нескольких клавиш на клавиатуре БК - см. № 1 за 1993 г., с . 129. - Прим. ред.)
Прочие операторы
- NOP - «пустая» операция. Используется при отладке, если нужно временно исключить какую-то команду, или для создания небольшой задержки (около 4 мкс). Не делает ничего. Код этой команды - 240.
К данной группе относятся также команды изменения признаков, т.е. разрядов 00...03 ССП. Используются они редко.
- CLN - очистка N.
- CLZ - очистка Z.
- CLV - очистка V.
- CLC - очистка C.
- CCC - очистка всех разрядов.
- SEN - установка N.
- SEZ - установка Z.
- SEV - установка V.
- SEC - установка C.
- SCC - установка всех разрядов.
(Существуют и другие машинные команды, обеспечивающие очистку или установку одновременно двух или трёх признаков, но они не имеют сокращённой ассемблерной записи. Их коды приведены в Приложении 4 к книге: Осетинский А.Г., Осетинский М.Г., Писаревский А.Н. Фокал для микро- и мини-компьютеров. Л.: Машиностроение, 1988. - Прим. ред.)
Псевдооператоры
Как уже неоднократно говорилось, каждый оператор с принадлежащими ему операндами (если они имеются) при трансляции переводится в машинный код, образуя машинную команду, или инструкцию. Но имеется ряд специфических задач, для решения которых обычных операторов недостаточно (например, резервирование места под константы, переменные и массивы, запись в память текстов (вы помните, как неуклюже мы делали это в наших прежних примерах?), символьных и цифровых последовательностей и т.п.) Этой цели служат псевдооператоры, или псевдокоманды.
В отличие от обычных операторов, псевдокоманды при трансляции не переводятся в машинные коды, они представляют собой лишь указания транслятору, что записать в память с текущего адреса или что выполнить при трансляции. Заносимая в память информация, если она имеется, следует после псевдооператора. Рассмотрим, какие псевдооператоры имеются в ассемблерах «МИКРО». Все они начинаются с символа «точка», который в ассемблере означает текущий адрес. Таким образом, любой псевдооператор можно истолковать как указание транслятору: «записать с текущего адреса...».
- .E - обнуление слова или байта и приведение текущего адреса к чётному. Как известно, команды ассемблера после трансляции занимают от одного до трёх машинных слов. Но слово это два байта, а значит, любая команда может после трансляции начинаться лишь с чётного адреса. Однако бывают ситуации, когда при написании программы мы не можем с определённостью сказать, чётным ли будет текущий адрес. Это случается, например, если в текст программы вписана последовательность символов или байтовых кодов С другой стороны, при выводе, скажем, текстовых сообщений удобно отмечать конец текста нулевым байтом. Обе задачи решаются одновременно, если в конце текста (или последовательности кодов) стоит псевдооператор .E. Встречая его, транслятор заносит в память нулевой байт, если адрес нечётный, или нулевое слово, если чётный. Адрес, таким образом, всегда приводится к чётному. Кроме того, данный псевдооператор можно применять для резервирования отдельных слов памяти под переменные, если их исходное значение - ноль.
- .+Х - зарезервировать X байт ОЗУ. Встречая в тексте этот псевдооператор, транслятор обнуляет указанное количество байтов (вместо X записывается конкретное восьмеричное число) и увеличивает на X текущий адрес. Применяется для выделения памяти под массивы, приведения текущего адреса к нужному, «подгонки» длины программ под «круглое» число и т.п. Пусть, например, мы с помощью оператора EMT 10 будем вводить текст максимальной длиной 20 байт, а начало буфера текста соответствует метке Т1. Тогда для выделения буфера под текст достаточно записать: Т1: .+20.
- .#Х или .#MET - запись константы в слово по текущему адресу. Константой может быть как восьмеричное число (без знака или со знаком «минус»), так и имя глобальной метки (можно и с коррекцией адреса в виде выражений MET+Х или MET-Х, где X - восьмеричное число). В составе псевдокоманды может быть через запятую перечислено несколько констант, например: .#14563, ТЕХ+140, Т27, -20, 24, 0, 532.
Каждая из перечисленных таким образом констант записывается в очередное машинное слово. Если константой является адрес метки, то его абсолютное значение транслятор заносит только после компоновки программы. В менее совершенных версиях ассемблеров допускается запись после .# только одного значения константы, но зато часто разрешается писать по несколько псевдооператоров в строке.
Этот псевдооператор широко применяется для записи констант, переменных (с заданием их исходного значения), а также строк абсолютных адресов меток, которые можно затем использовать для адресации различных модулей программы.
Приведём пример такой записи. Пусть в программе имеется 10 модулей, помеченных метками М0, M1, М2, ... М9. а директивы передачи управления этим модулям - вводимые с клавиатуры цифры «0», «1», «2», ... «9» соответственно. Как оптимально организовать блок управления такой программой? Запишем:
BEG: EMT 6 ; Ввод директивы ASL R0 ; Умножить на 2 код цифры JMP @ADR-140(R0) ; Передача управления модулю ADR: .#М0,М1,М2,M3,М4,М5,М6,М7,М8,М9
Первая строка программы вполне понятна, мы заносим с клавиатуры в R0 код директивы, причём нулю соответствует код 60, единице - 61, и т.д. В последней же строке записаны адреса меток модулей, на которые мы должны передать управление. Адрес каждой метки, естественно, занимает одно слово, а значит, адреса следуют с интервалом два байта, а коды управления 60, 61... и т.д. - с интервалом в один байт. Чтобы привести последний в соответствие с интервалом следования меток, во второй строке мы умножаем код на 2. А вот в третьей строке, которая, собственно, и передаёт управление, использованы все возможные ухищрения адресации. Мы передаём управление по адресу, который, в свою очередь, записан по адресу (двойная косвенность!), определяемому выражением: R0+#ADR-140. А число 140 есть не что иное как удвоенный код символа «0». Таким образом, если, например, мы вводим директиву «3», в результате у нас получается 63*2+#ADR-40 = #ADR+6. А по адресу #ADR+6 записан адрес метки М3, на неё и будет передано управление. Отметим, что наша программа не имеет защиты от ошибок - если мы введём директиву, код которой больше кода клавиши «9» или меньше, чем у «0», результат будет непредсказуем. Следовало бы после оператора EMT 6 проверять содержимое R0 на соответствие заданному диапазону кодов, например так:
CMPB '0',R0 BHI BEG CMPB '9',R0 BLO BEG
(В первоначальном тексте примера мы этот фрагмент не привели, заботясь о его максимальной простоте.)
- .B: - запись константы в байт по текущему адресу. В качестве констант могут использоваться только восьмеричные числа от 0 до 377. Несколько чисел после одного псевдооператора может быть записано через запятую (в ранних версиях - только одно число). Эта псевдокоманда применяется в основном для записи строк кодов управления режимами вывода на экран или для простейшей генерации звука (код 7 - щелчок как при нажатии на клавишу). Можно также, записав строку кодов клавиш управления, найти в ней заданный код (директиву) и передать управление на модуль программы, пользуясь, в свою очередь, строкой меток, например так:
BEG: EMT 6 ; Ввод директивы MOV #DIR,R4 ; Начало таблицы кодов директив 0: CMPB R0,(R4)+ ; Код совпадает? BEQ 1 ; Да - передать управление TSTB @R4 ; Конец таблицы? BNE 0 ; Нет - продолжать поиск кода BR BEG ; Иначе (кода в таблице нет) идём к началу 1: SUB #DIR,R4 ; Номер кода директивы ASL R4 ; Умножить на 2 JMP @ADR-2(R4) ; Передать управление по таблице ; меток, в соответствии с номером ; кода директивы, с поправкой DIR: .B:10,22,31,32,33.E ADR: .#М1,М2,M3,М4,М5
В данной программе мы используем две таблицы. В первой, под меткой DIR содержится список кодов директив (используются, для примера, коды клавиш управления курсором). Введя директиву, мы ищем её код в таблице. Если он найден, вычисляем его номер от начала таблицы. Но коды директив - байты, а адреса меток передачи управления - слова, поэтому удваиваем номер и вычисляем адрес соответствующей метки в таблице ADR (как адрес начала таблицы плюс номер метки). По этой метке и передаётся управление. Поясним, что запись @ADR-2 появилась в программе потому, что после нахождения директивы содержимое регистра R4 инкрементируется и указывает на адрес следующей директивы, из-за чего при обращении к метке мы должны ввести поправку. Псевдокоманда .E в конце таблицы DIR приводит адрес метки ADR к чётному, а нулевой байт отмечает конец таблицы. Чтобы всё было окончательно ясно, покажем, как можно организовать модули программы, к которым мы обращаемся таким образом. Пример выбран простейший - по нажатиям клавиш-директив наша программа будет выдавать имена меток (но так можно построить любые модули). После программы, введённой в предыдущем примере, допишем:
М1: MOV 'M1',R0 ; М1 - «стрелка влево» BR TYP М2: MOV 'М2',R0 ; М2 - «стрелка в начало экрана» BR TYP M3: MOV 'M3',R0 ; M3 - «стрелка вправо» BR TYP М4: MOV 'M4',R0 ; М4 - «стрелка вверх» BR TYP М5: MOV 'M5',R0 ; М5 - «стрелка вниз» BR TYP TYP: EMT 16 ; Печать на SWAB R0 ; экране имени EMT 16 ; метки BR BEG ; К началу END
Данный способ передачи управления интересен тем, что таблицы могут расширяться неограниченно, по мере написания новых модулей программы. Если же вместо оператора JMP @ADR-2(R4) использовать CALL @ADR-2(R4), то в качестве модулей можно использовать подпрограммы, после исполнения которых управление будет возвращаться в основную программу, - иногда такой способ удобнее.
- .@MET - смещение к метке. При трансляции в слово по текущему адресу заносится разность между адресом метки и текущим адресом - смещение к метке. Как обычно, можно ввести в это смещение поправку, записав псевдооператор в виде .@MET+Х или .@MET-Х, где X - восьмеричное число. Эта псевдокоманда широко используется для получения абсолютных адресов меток в перемещаемых программах. В самом деле, когда мы заносим абсолютный адрес метки, например, оператором MOV #MET,R4, второе слово данной команды заносится в память один раз при компоновке, а его значение зависит от заданного начального адреса, по которому программа компонуется и в дальнейшем будет работать. Стоит только изменить адрес загрузки такой программы, и она работать не будет, так как действительные адреса меток перестанут совпадать с полученными при компоновке. Иначе дело обстоит, если задано смещение: как бы мы ни перемещали программу, «расстояние» от текущего адреса до метки будет неизменным. Как пользоваться данной псевдокомандой? Предположим, что нам нужно получить в каком-либо регистре абсолютный адрес метки MET. Напишем программу:
MOV PC,R4 ; Занести текущий адрес ADD (PC)+,R4 ; Прибавить смещение .@MET+2 ; Смещение с поправкой на +2
Мы получили абсолютный адрес метки MET в регистре R4. Каким образом это делается? Вначале мы записали в R4 текущий адрес. Во второй строке прибавили к этому адресу смещение, полученное с помощью псевдооператора .@ в третьей строке. Мы добились этого, косвенно обращаясь через регистр PC, в котором в этот момент находится адрес следующей команды, т.е. как раз нужной нам строки Но в момент обращения к PC в первой строке мы записали адрес, на 2 меньший, чем адрес строки псевдооператора, и вынуждены исправить эту ошибку, внеся поправку в смещение («+2»), То, что мы во второй строке не просто косвенно обращаемся к PC, а с инкрементом, служит важной цели - увеличить содержимое PC на 2 и таким образом «перескочить» строку с псевдооператором: передавать на неё управление нельзя, там не команда, а произвольное число. Пользуясь таким приёмом, можно получить адрес метки независимо от того, находится она до или после псевдооператора. Как всегда в таких случаях, метка должна быть глобальной, а не локальной. Возможны и иные приёмы получения абсолютных адресов меток с помощью данного псевдооператора, но предложенный - самый экономичный по расходу памяти и быстродействию.
Все вышеперечисленные псевдооператоры могут быть записаны как в одну строку друг за другом в любой последовательности, так и в отдельных строках, но при этом надо учитывать, что псевдокоманды .# и .@ могут правильно работать только при их трансляции по чётным адресам. В псевдооператоре .@ можно, в отличие от .#, указывать только одно имя метки, перечислять их через запятую нельзя. В записанной последовательности псевдооператоров недопустимы пробелы и прочие символы, разрывающие её. Такой символ либо ведёт к ошибке, либо считается концом строки - и всё, следующее за ним, транслятор игнорирует.
- .A: - запись строки символов в коде КОИ-8. Код КОИ-8 (а точнее, одна из его модификаций) - это «родной» символьный код БК-0010, включающий все знаки и коды управления в диапазоне 0...377. (Иногда этот код называют «кодом ASCII» по аналогии с американским стандартом. Но, строго говоря, код ASCII - это совсем другой стандарт и никакого отношения к БК не имеет. Достаточно сказать, что он, естественно, не содержит русских символов.) Так вот, данный псевдооператор позволяет записать в строку любые символы БК-0010, а при трансляции их коды будут занесены в память начиная с текущего адреса, причём код каждого символа занимает один байт. Поскольку всё, что следует за .A:, транслятор интерпретирует как символьный код, то этот псевдооператор может быть в строке только последним - после него недопустимы никакие операторы или комментарии. Во всех версиях «МИКРО.К» пробелы заносятся в строку символов в любом месте обычным образом, в версиях же «МИКРО.С» занесённые пробелы не всегда транслируются правильно (в частности, игнорируются пробелы в конце строки). Если в строку символов нужно ввести управляющие коды (перевод строки, сброс экрана и т.п.). их можно задать с помощью псевдооператора .B. Для самых распространённых кодов управления в «МИКРО.10К» последних версий введена возможность вставлять их в строку в виде символов «^» (возведение в степень) и «\» (обратная косая черта), которые обозначают, соответственно, код 12 (перевод строки) и код 0. Этот псевдооператор очень широко применяется для ввода в состав программ меню, текстовых сообщений, инструкций, таблиц кодов и т.п. Примеры его использования будут приведены чуть позже.
- .R: - запись строки символов в коде RADIX-50. Как уже было сказано, при кодировании текста в коде КОИ-8 каждый символ занимает один байт. Это и понятно, ведь используются все коды в диапазоне 0...377. Но иногда достаточно и меньшего количества кодов, например только цифр и латинских символов. Простой расчёт показывает, что если ограничиться только 40д символами, то в каждом машинном слове теоретически можно разместить до трёх символов (если не верите, извлеките корень третьей степени из числа 65536д). Но такое размещение явно не пройдёт «напрямую». В самом деле, если разделить 16 на 3, получится 5 и 1 в остатке. А в пяти битах можно закодировать не более 32д символов. Чтобы можно было полностью использовать информационную ёмкость машинного слова, прибегают к следующему приёму. Весь кодируемый текст делят на триады символов, а затем в каждое слово записывают одну из триад по правилу: значение = ((C1 * 50) + С2) * 50 + С3, где C1, С2 и С3 - первый, второй и третий коды символов (все числа и вычисления - в восьмеричной системе). Десятичное число 40 равно восьмеричному 50, отсюда и название кода. Всё это позволяет при ограниченном наборе символов достичь повышения плотности упаковки текста в полтора раза и применяется, когда необходимо упаковать длинный текст при максимальной экономии памяти. Упаковка и распаковка кода RADIX-50 не слишком простая задача, требующая заметного времени по сравнению с обработкой текстов в формате КОИ-8, что наряду с ограниченным набором символов является препятствием для широкого применения RADIX-формата. Приведём таблицу восьмеричных значений кода RADIX-50:
Символ |
Код RADIX-50 |
---|---|
ПРОБЕЛ |
0 |
A...Z |
1...32 |
$ |
33 |
. |
34 |
Резерв |
35 |
0...9 |
36...47 |
Код 35 не используется, а код 34 не задействован в «МИКРО», что называется «по техническим причинам». Символ, не входящий в состав кода RADIX, считается концом строки. Если количество преобразуемых символов не кратно 3, то последние один или два символа автоматически дополняются до полной триады пробелами. Псевдооператор .R, как и .A, должен быть последним в строке.
★ ★ ★
Осталось рассмотреть последний оператор - END. Он вполне может быть отнесён к псевдооператорам, так как не транслируется, а только сообщает транслятору о конце текста программы. Весь текст, следующий за ним, транслятор игнорирует. Хотя этот оператор не обязателен в «МИКРО.10К» и в некоторых иных версиях ассемблера, всё же лучше писать его в конце программы во всех случаях. Помимо основного назначения, он может применяться, если нужно оттранслировать только часть программы, в этом случае оператор END временно ставится в конце транслируемой части.
А теперь приведём примеры использования разобранных выше псевдооператоров.
1) ; Вывод на экран текста с предварительным сбросом экрана ; и переключением формата MOV #TEX,R1 ; Адрес начала текста CLR R2 ; Конец текста - нулевой байт EMT 20 ; Вывод текста HALT ; Останов ТЕХ: .B:14,233 .A:^ Привет от БК-0010, .A:^ лучшего в мире компьютера!\ END 2) ; Программа "мигания" экрана с выдачей звуковых сигналов 0: MOV #COM,R1 ; Адрес начала команд CLR R2 ; Признак конца команд - 0 EMT 20 ; Выдача команд звука и мигания EMT 6 ; Ждать нажатия клавиши BR 0 ; Программа зациклена COM: .B:7,7,7,235,7,7,7,235.E END 3) ; Перемещаемая программа вывода текста 0: MOV PC,R1 ; Занесение физического ADD (PC)+,R1 ; адреса метки ТЕХ .@ТЕХ+2 ; в R1 CLR R2 ; Конец текста - нулевой байт EMT 20 ; Вывод текста EMT 6 ; Ожидание нажатия клавиши BR 0 ; Программа зациклена ТЕХ: .A:^ Эта программа может работать по любому адресу, .A:^ и не нуждается в компоновке!\ END 4) ; Программа ввода текста в буфер с его последующим выводом MOV #TEX,R1 ; Адрес буфера текста MOV #5377,R2 ; Конец текста - 377 байт или "ВВОД" EMT 10 ; Ввод текста с клавиатуры MOV #TEX,R1 ; Адрес начала текста MOV #5377,R2 ; Ограничители текста EMT 20 ; Вывод текста HALT ; Конец ТЕХ: .+377 ; Буфер текста 377 байт END 5) ; Программа выдачи нескольких последовательных ; фрагментов текста MOV #TEX,R1 ; Адрес начала текста CLR R2 ; Признак конца - нулевой байт EMT 20 ; Выдать первый текст EMT 6 ; Ждать нажатия клавиши CLR R2 EMT 20 ; Выдать второй текст EMT 6 CLR R2 EMT 20 ; Выдать третий текст EMT 6 CLR R2 EMT 20 ; Выдать четвёртый текст HALT ТЕХ: .A:^ Нажимайте клавиши!\ .A:^ Вы нажали первую клавишу.\ .A:^ А теперь - вторую!\ .A:^ Наконец-то, последняя клавиша! До свидания. .E END
Заметим, что в последнем примере при вызове EMT 20 используется не только входная информация, но и выходная - для выдачи последующих фрагментов текста их адрес в R1 уже не заносится, так как там содержится адрес байта, следующего за концом предыдущего текста.
Нельзя не упомянуть и о чрезвычайно оригинальном и очень полезном на практике способе вывода текстовых сообщений, также использующем выходную информацию EMT 20. Этот способ предложен учеником X класса Денисом Батранковым (Московская обл.). Поясним его смысл на примере:
JSR R1,PRI ; Печать текста ТЕ1: .A:Привет!^ .E JSR R1,PRI ; Печать текста ТЕ2: .A:Два привета!^ .E JSR R1,PRI ; Печать текста ТЕЗ: .A:Три привета!^ .E ;............... ; Подпрограмма печати PRI: CLR R2 ; Признак конца текста EMT 20 ; Вывод текст? INC R1 ; Привести содержимое R1 BIC #1,R1 ; к чётному значению RTS R1 ; Выход ;...............
Здесь используется та особенность, что при обращении к подпрограмме JSR RN,MET в регистр RN заносится адрес возврата, т.е. адрес следующей после оператора JSR команды, а там у нас - начало текста. В подпрограмме выполняется EMT 20 по содержимому R1 (с предварительной установкой признака окончания по нулевому байту или слову), а затем содержимое R1 (в котором после выполнения EMT 20 хранится адрес следующего байта после текста) приводится к ближайшему большему чётному адресу, т.е. в R1 оказывается адрес возврата из подпрограммы на следующую после текста команду. Данный способ даёт заметную экономию памяти, если текстовых сообщений в программе много, и при этом обеспечивает её перемещаемость. К его недостаткам можно отнести необходимость помещать текстовые сообщения в программе только по ходу обращений к ним (хотя в большинстве случаев это как раз и требуется) и недопустимость использования в качестве ограничителя текста символа «\» (допускается только оператор .E). В остальном же этот способ имеет неоспоримые преимущества перед всеми остальными, включая и то, что после вывода текста содержимое R1 восстанавливается, как при всяком выходе из подпрограммы по RTS RN (Кстати, обозначать метками начала текстовых сообщений, как это сделано в нашем примере для наглядности, совсем не обязательно. Это может пригодиться, только если потребуется ещё раз вывести то же сообщение в другом месте программы).
Контрольные вопросы и задания
- Напишите программу, которая бы «отслеживала»
положение символьного курсора по горизонтали, перемещая наравне с ним
стрелку в служебной строке.
-
0: EMT 6 ; Ввод символа (команды) EMT 16 ; Вывод символа на экран EMT 26 ; Позиция курсора (R1 = X) CLR R0 ; Очистка EMT 22 ; служебной строки MOV #33,R0 ; Код "стрелка вниз" EMT 22 ; Символ в служебную строку (позиция - в R1) BR 0 ; Программа зациклена END ; Конец
-
- Напишите программу, которая бы, определяя установленный режим дисплея, «приказывала»
пользователю печатать символы только в русском регистре.
-
0: EMT 6 ; Ввод символа TSTB @#43 ; Регистр "РУС"? BNE 1 ; Да - печать символа, MOV #TEX,R1 ; иначе – выдать CLR R2 ; предупреждение EMT 20 ; о вводе не русского символа BR 0 ; К началу 1: EMT 16 ; Символ на экран BR 0 ; Программа зациклена ТЕХ: .A:^ Нажмите клавишу "РУС"!^ .E END ; Конец
-
- Напишите простейший копировщик - программу, которая бы после запуска
позволяла скопировать последний загруженный с магнитной ленты по «естественному»
адресу файл без задания его имени, адреса и длины с клавиатуры. Программа
должна быть перемещаемой, с целью размещения её в зоне ОЗУ, не занимаемой
копируемым файлом.
-
Подсказка: блок параметров EMT 36 для записи не обязательно начинается с адреса 320 а программа должна получиться очень короткой - всего несколько операторов.
MOV #344,R1 ; Адрес блока параметров послед- ; него загруженного файла минус 2 ; используем как блок параметров файла ; для записи MOV #2,@R1 ; Команда "Запись" EMT 36 ; Запись файла HALT ; Конец END
-
- Что произойдёт, если после запуска следующей программы нажать любую
символьную клавишу:
MTPS #340 WAIT EMT 6 EMT 16
-
- Ничего, так как приоритет ЦП установлен равным 7, а приоритет клавиатуры - 4. Программа остановится на команде WAIT, и её работа будет продолжена после нажатия на клавишу «СТОП».
-
Как известно, основное преимущество ассемблера перед прочими языками - скорость исполнения программ. Согласно технической документации, для БК-0010 она равна 300 тыс. операций в секунду. Но это не более чем абстрактная цифра, имеющая очень мало общего с фактическим быстродействием ЭВМ. Скорость исполнения, программы, состоящей из реальных команд, зависит от многих факторов, из которых главными являются тип команд и способы адресации. С помощью специального устройства (электронного измерителя длительностей) в сочетании со специально написанными для этой цели программами скорость исполнения отдельных команд может быть непосредственно измерена, а затем путём логических рассуждений и расчётов можно найти правила для вычисления времени исполнения любых команд БК-0010 с любыми методами адресации. Автор проделал это исследование, результаты которого изложены ниже. Следует заметить, что правила вычисления времени исполнения команд для БК-0010 оказались заметно отличающимися от аналогичных для «прародителя» БК - компьютера PDP-11. Это естественно, так как эти ЭВМ, несмотря на одинаковую систему машинных команд, заметно различаются по внутренней структуре. Приступим к рассмотрению результатов, полученных на БК-0010.
Заметим сразу, что способы измерения времени исполнения команд могут быть различными и в зависимости от этого получаются заметно отличающиеся результаты. В частности, Б.Ф. Фролкин совершенно правильно обратил внимание автора на тот факт, что на время исполнения команд сильно влияет время отклика ОЗУ БК-0010 из-за особенностей работы контроллера ОЗУ К1801ВП1-037. Поэтому в конкретной программе время исполнения может несколько отличаться от вычисленного (в том числе это зависит и от типа предшествующей команды). Программы в ПЗУ БК-0010 исполняются несколько быстрее, чем в ОЗУ (по данным автора, увеличение скорости в ПЗУ может составлять от 10 до 30%, что в основном совпадает с цифрами, предоставленными Б.Ф. Фролкиным). При использовании приведённых ниже таблиц необходимо также учитывать следующее:
- полученные автором данные эмпирические и приближенные;
- данные относятся к программам, работающим в ОЗУ пользователя БК-0010.
Временные характеристики команд БК-0010
Прежде всего необходимо отметить, что тактовая частота процессора, установленного в БК-0010, в совокупности с особенностями этой ЭВМ обеспечивает реальное быстродействие примерно 250 тыс. операций в секунду. Но что это за операции? Речь идёт об операциях типа «регистр-регистр», т.е. об адресации RN (с кодом 0). При всех прочих способах адресации скорость исполнения команд уменьшается. Время исполнения команд с кодом адресации 0 или безоперандных носит название основного времени команды, для БК-0010 оно равно 4.0 мкс (1 мкс = 0.000001 с). Таким образом, за 4.0 мкс исполняются операторы NOP, CLC, CLV, CLZ, CLN, CCC, SEC, SEV, SEZ, SEN, SCC, а также все одно- и двухоперандные команды, т.е. CLR, INC, ASL, ASR, MTPS, SWAB, MOV, CMP, XOR, и прочие, если способ адресации операндов в них - RN (код 0).
Сложнее определить время исполнения команд при иных способах адресации. Вспомним, как мы обозначали операнды при описании операторов. Буквой А обозначался операнд-источник, а В - операнд-приёмник в двухоперандных командах, например MOV А,В. Пара букв АВ означала источник (он же и приёмник) в однооперандных командах, например COM AB. Как A1, А2 обозначались источник и приёмник, не изменяющиеся в процессе исполнения команды, например CMP А1,А2. И наконец, операнд - адрес перехода обозначался как N. Используя те же обозначения, приводим таблицу времени адресации операндов для БК-0010.
Адресация |
Тип операнда и добавочное время, мкс |
|||||
---|---|---|---|---|---|---|
способ |
код |
А,А1 |
АВ |
В |
А2, Nj |
Ns |
RN |
0 |
0 |
0 |
0 |
0, - |
- |
@RN, (RN)+, -(RN), #Х, #MET |
1, 2, 4 |
4.0 |
5.4 |
6.8 |
6.8 |
10.8 |
@(RN)+, @-(RN), X(RN), MET(RN), @#X, MET |
3, 5, 6 |
6.8 |
8.0 |
10.8 |
9.4 |
13.4 |
@X(RN), @MET(RN), @MET |
7 |
9.4 |
10.8 |
13.4 |
12.0 |
16.0 |
Как пользоваться таблицей? Мы уже говорили, что основное время для всех команд равно 4.0 мкс. Для вычисления времени исполнения однооперандной команды к основному времени необходимо прибавить время адресации операнда из таблицы. Для примера вычислим время исполнения ряда команд:
Команда |
Тип операнда, |
Время исполнения осн. |
Сумма, мкс |
|
---|---|---|---|---|
TST R2 |
A1 |
0 |
4.0 + 0.0 |
4.0 |
TST @R4 |
A1 |
1 |
4.0 + 4.0 |
8.0 |
COM @R2 |
АВ |
1 |
4.0 + 5.4 |
9.4 |
MTPS #340 |
АВ |
2 |
4.0 + 5.4 |
9.4 |
CLR @#256 |
АВ |
3 |
4.0 + 8.0 |
12.0 |
INC @-(R4) |
АВ |
5 |
4.0 + 8.0 |
12.0 |
ROR MET |
АВ |
6 |
4.0 + 8.0 |
12.0 |
NEG @10(R5) |
АВ |
7 |
4.0 + 10.8 |
14.8 |
С двухоперандными командами дело обстоит значительно сложнее, и для приближенного вычисления их времени исполнения можно предложить следующие правила. Для операнда-источника время берётся из таблицы без всяких оговорок, тип операнда - А или А1 (в зависимости от конкретного оператора). А вот для операнда-приёмника принимается тип В только тогда, когда источник - RN, в противном же случае тип приёмника берётся АВ. Для операторов CMP и BIT, которые не меняют операнды, естественно, время адресации второго операнда короче: если источником является RN, то для приёмника берётся тип А2, при ином же способе адресации источника тип приёмника принимается А1. При работе с оператором XOR для второго операнда принимается тип А2. Все эти сложности объясняются порядком, в котором процессор обрабатывает операнды на микропрограммном уровне, и уже отмеченными особенностями самой ЭВМ. Примеры:
Команда |
Тип операнда, |
Время исполнения, осн. |
Сумма, мкс |
|
---|---|---|---|---|
MOV R2,R4 |
А, В |
0, 0 |
4.0 + 0.0 + 0.0 |
4.0 |
MOV R2,@R4 |
А, В |
0, 1 |
4.0 + 0.0 + 6.8 |
10.8 |
MOV @R2,R4 |
А, АВ |
1, 0 |
4.0 + 4.0 + 0.0 |
8.0 |
MOV @R2,@R4 |
А, АВ |
1, 1 |
4.0 + 4.0 + 5.4 |
13.4 |
SUB @MET,R5 |
А, АВ |
7, 0 |
4.0 + 9.4 + 0.0 |
13.4 |
ADD MET,MET |
А, АВ |
6, 6 |
4.0 + 6.8 + 8.0 |
18.8 |
CMP #100,R5 |
А, А1 |
2, 0 |
4.0 + 4 0 + 0.0 |
8.0 |
CMP R5,#100 |
А, А2 |
0, 2 |
4.0 + 0.0 + 6.8 |
10.8 |
BIT TV,@#256 |
А, А1 |
2, 3 |
4.0 + 4.0 + 6.8 |
14.8 |
XOR R3,MET |
А, А2 |
0, 6 |
4.0 + 0.0 + 9.4 |
13.4 |
Отметим, что время исполнения команд на БК-0010 не зависит от того, работает ли команда с байтом или со словом.
Ещё ряд операторов имеет время исполнения, которое должно быть оговорено особо.
Оператор или группа операторов |
Общее время исполнения, мкс |
---|---|
JMP N |
см. табл. 1, столбец Nj |
JSR RN,N (или CALL N) |
см. табл. 1, столбец Ns |
RTS RN (или RET) |
10.8 |
BR MET, BEQ MET и все прочие операторы ветвления независимо от того, выполняется ли переход по условию |
5.4 |
SOB RN,MET независимо от того, выполняется ли ветвление |
6.8 |
EMT, TRAP, BPT, IOT |
22.8 |
HALT |
48.0 |
Прерывания от внешних устройств |
~20 мкс + время завершения исполнения текущей команды |
RTI, RTT |
13.4 |
RESET |
~380 мкс |
Приведём также время отработки некоторых часто используемых прерываний EMT БК-0010, что представляет интерес с практической точки зрения. Время приводится в миллисекундах (1 мс = 1000 мкс).
Прерывание EMT |
Общее время исполнения (приближённо) |
---|---|
EMT 4 |
0.2 мс |
EMT 14 |
240 мс |
EMT 16 |
от 0.4 (R0 = 0) до 1.7 мс (в R0 код символа); с погашенным курсором вывод символа - 1.1 мс; в режиме «ИНВ.С» время вывода больше на 100 мкс, в режиме «ПОДЧ.» - на 20 мкс; в режиме «32 символа в строке» все значения возрастают примерно вдвое; сброс экрана - 200 мс; перевод строки - 1.8 мс (с рулонным сдвигом - 18 мс) |
EMT 20 |
(1.6 * N) + 0.5 мс, где N - число выводимых символов; в спецрежимах (инверсия, подчёркивание, 32 символа) - поправка на каждый символ как для EMT 16 |
EMT 22 |
от 1.1 (вывод символа) до 34 мс (сброс строки); в спецрежимах - поправка аналогично EMT 16 |
EMT 24 |
1.8 мс (в символьном режиме); 2.4 мс (в графическом режиме) |
EMT 26 |
0.25 мс (в символьном режиме); 0.27 мс (в графическом режиме) |
EMT 30 |
0.5 мс независимо от кода операции |
EMT 32 |
(0.5 * N) + 0.5 мс, где N - число точек вектора (независимо от кода операции); время несколько варьирует в зависимости от направления черчения (наименьшее время - для горизонтальных и вертикальных векторов) |
Контрольные вопросы и задания
- Что выгоднее с точки зрения скорости работы ЭВМ ставить на первое
место в двухоперандных командах с разными способами адресации: «быстрый»
или «медленный» операнд? Подсчитайте время исполнения команд
CMP @MET,RN
и CMP RN,@MET.
-
Медленный, при этом общая скорость исполнения команд заметно возрастает. Время - 13.4 и 16 мкс соответственно.
-
- Что исполняется быстрее: обращение к подпрограмме или переход к
обработке командного прерывания?
-
Обращение к подпрограмме намного быстрее: JSR RN,N + RTS RN даже в самом худшем случае (код адресации 7) требует 26.8 мкс, а, например, EMT + RTI - 36.2 мкс. Это не говоря уже о том, что для обращения по EMT или TRAP необходимо потратить значительное время ещё и на извлечение и обработку команды программой-диспетчером .
-
- Подсчитайте, сколько времени займёт исполнение команд:
MOV #12,R0 EMT 16
-
Первая команда - 8 мкс, вторая, если не происходит рулонный сдвиг экрана, - 1.8 мс (при сдвиге - 18 мс). Очевидно, что по сравнению со второй командой время исполнения первой практически равно нулю.
-
- Как лучше (с точки зрения быстродействия ЭВМ) организовать ввод
чисел-констант в программе:
- ... каждый раз вписывая в нужное место программы операнд #Х (например, MOV #1000,R0),
- ... составив таблицу констант и обращаясь к ней косвенно через регистр (например, MOV (R1)+,R0),
- ... составив таблицу констант, обозначив их метками и обращаясь к ним по меткам (например, MOV MET,R0)?
-
Первым или вторым способом: они равнозначны по времени адресации, тогда как третий способ медленнее примерно в 1.5 раза. Но нужно учесть, что для обращения к таблице по второму способу необходимо ещё, как правило, предварительно занести в RN адрес константы в таблице. Поэтому явно предпочтительнее, если констант не слишком много, просто напрямую указывать их в качестве операндов, например MOV #1250.R0, а не составлять из них таблицы.
- Пусть нужно вывести текст из N символов.
Известно, что текст при убранном курсоре выводится быстрее. Начиная
с какого значения N становится выгодно по времени
убрать курсор, а потом его восстановить? Считайте, что команда гашения
или восстановления курсора исполняется за 1.1 мс.
-
Составим уравнение 1.7*N = 2 *1.1 + 1.1*N и решим его относительно N. Получается, что N примерно равно 3.67. Ответ: выгодно убирать и затем восстанавливать курсор, если текст состоит из 4 и более символов, т.е. практически всегда.
-
★ ★ ★
Итак, можно считать, что мы изучили в общих чертах архитектуру и ассемблер БК-0010. Что же дальше? Можно ли считать художником человека, который изучил химический состав красок, правила нанесения грунта на холст, способы изготовления рам для картин? Ясно, что нет. Пора, наверное, перейти к самим картинам, то бишь, к программам... И начнём мы с вопроса, которого уже касались, - с векторов прерывания, но теперь посмотрим на них не как теоретики, а с практической точки зрения: что можно сделать с их помощью (что делает ЭВМ - мы уже знаем).
Векторы прерывания и программирование
Большинство случаев практического использования сведений о векторах прерывания сводится к изменению этих векторов и созданию новых программ обработки прерываний. Например, таким путём можно запретить останов программы клавишей «СТОП», ввести дополнительные команды процессора, дополнительные символы клавиатуры, да и мало ли что ещё.
Перейдём сразу к конкретным примерам. Предположим, что нам необходимо запретить остановку программы клавишей «СТОП», - это бывает необходимо во многих случаях, когда останов может привести к нежелательным последствиям. Поскольку клавиша «СТОП» внеприоритетная и просто запретить прерывание по вектору @#4 мы не можем, нам остаётся только изменить его содержимое и написать новую программу обслуживания прерывания. Включим в основную программу (где-нибудь в её начале) такую команду: MOV #V4,@#4. С её помощью сразу же после запуска мы перепишем вектор @#4. Теперь при нажатии на клавишу «СТОП» (и, равным образом, при отработке команды HALT) управление будет передаваться не по прежнему адресу, а на метку V4. Какую же программу там разместить? Это зависит от наших целей.
Если нужно только, чтобы клавиша «СТОП» вообще «не работала», достаточно, как может показаться, единственной команды возврата из прерывания RTI, её выполнением обработка прерывания по вектору @#4 и закончится:
V4: RTI
Но не всё так просто. Необходимо учитывать, что клавиша «СТОП» может быть нажата в совершенно произвольный момент и прерывает она программу не совсем так, как, скажем, команда EMT или нажатие клавиши на клавиатуре. При прерывании по клавише «СТОП» процессор может не закончить выполнение очередной команды, но при этом в стеке будет сохранено значение PC, соответствующее адресу следующей, с неё-то и продолжится выполнение программы после отработки нашего RTI. В зависимости от конкретных команд такой «финт» может привести к самым неприятным последствиям, вплоть до зависания системы. Как же тогда быть? Надо поступить хитрее. Необходимо предусмотреть в программе специальный, так называемый «холодный» вход (т.е. адрес, при передаче управления на который не произойдёт нарушения работы программы). Впрочем, правильнее всего было бы называть его повторным входом в программу На этот вход и надлежит передать управление по клавише «СТОП», для чего его адрес указывается в качестве вектора прерывания. Пусть повторный вход в нашей программе (например, игре) - по метке RBG. Напишем:
V4: MOV #1000,SP JMP RBG
Вот и всё. Вторая команда ясна - это просто передача управления на повторный вход. А зачем первая, что мы записываем в регистр SP и для чего? Вспомним, как выполняется прерывание: в стек заносятся для сохранения ССП и PC. При этом, конечно, изменяется и указатель стека SP - его значение уменьшается на 4. Команда RTI в конце программы обработки прерывания не только восстанавливает ССП и PC, но и «очищает» стек, приводя его указатель в исходное состояние. Но в нашей программе её нет! Это значит, что при каждом нажатии «СТОП» стек будет «засоряться» всё новыми значениями ССП и PC, а он не безграничен. Рано или поздно стек будет исчерпан, и тогда... Вот для предотвращения этого и служит первая команда - она восстанавливает вершину стека, «чистит» его. Число же 1000 - это начальное содержимое SP в мониторе, но ничего страшного обычно не происходит, если такая вершина стека будет установлена в любом режиме.
А теперь предположим, что нам нужно кроме перезапуска программы по «СТОП» сохранить и возможность выхода из неё. Как быть? Напишем такую программу обработки прерывания:
V4: MOV #1000,SP ; Восстановить SP EMT 6 ; Ожидание нажатия клавиши CMPB #40,R0 ; Код ПРОБЕЛ ? BEQ 0 ; Да - к метке 0 JMP RBG ; Иначе - повторный вход 0: EMT 14 ; Восстановить векторы HALT ; Останов и выход в ПМ
Первым делом мы «чистим» стек. Затем следует команда EMT 6 - ожидание ввода символа с клавиатуры, его код будет записан в регистр R0. Если это код 40 - «ПРОБЕЛ», то должен последовать останов, для чего выполняется переход к метке 0. А что делает команда EMT 14? Она устанавливает состояние ЭВМ в «исходное положение», в том числе переписывает все векторы прерывания на стандартные адреса монитора. После этого команда HALT приводит к обычному результату - останову программы. Если же нажат не «ПРОБЕЛ», а иная клавиша, тогда перехода к метке 0 не будет и обработка прерывания закончится как предыдущая - перезапуском программы по метке RBG.
★ ★ ★
Довольно интересное применение может найти вектор @#10 - прерывание по резервному коду. Да что там может - уже нашёл! В Фокале он использован для обработки псевдокоманд и функций. Эти псевдокоманды имеют коды в диапазоне 7000...7377. Такие коды отсутствуют в наборе команд ЦП, и при их обнаружении происходит прерывание по вектору @#10. А этот вектор в Фокале «направлен» на специальную программу интерпретатора этого языка, которая выделяет код, вызвавший прерывание, и в зависимости от него передаёт управление подпрограммам реализации команд Фокала. Совершенно аналогично можно организовать и выполнение специальных дополнительных команд в программе пользователя, лишь бы выбранные коды отсутствовали в наборе команд ЦП БК-0010. Так, в литературе (см. Микропроцессорные средства и системы. 1988. №4. с. 47) описан эмулятор (эмуляция - имитация функционирования одной системы средствами другой) команд расширенной арифметики для ЭВМ ДВК-2, основанный как раз на этом принципе. Он позволяет исполнять на ДВК-2 программы. написанные для СМ-4 и «Электроники-60», не прибегая к их адаптации (переделке под другую систему команд)[1].
★ ★ ★
А теперь займёмся довольно сложным, но весьма интересным вектором @#14. Вам, наверное, приходилось отлаживать программы, и при этом, конечно, были случаи, когда вы никак не могли найти ошибку, а программа так и не желала работать как надо. При этом у вас возникало острое желание «заглянуть внутрь» работающей программы, посмотреть, в каком порядке выполняются команды и каковы результаты их действий. В большинстве языков высокого уровня такой трассировочный режим предусмотрен. А для программ в кодах существуют специальные программы - отладчики, позволяющие выполнять команды по одной. Как же они работают?
Если вы помните, при рассмотрении ССП (или PS) мы упоминали так называемый T-разряд (бит 04 ССП). Если записать туда «1», то работа программы будет прерываться после выполнения каждой команды, по вектору @#14. Но ведь это как раз то, что нужно для отладки, тот самый трассировочный режим! Перепишем вектор @#14 и разместим по указанному в нем адресу программу, которая, например, будет выдавать на экран адреса и коды выполняемых команд, а также содержимое регистров, значение ССП и пр. Прекрасно, давайте попробуем:
MTPS #20
Что получается? Ничего - этой командой T-разряд не изменить! А если бы она записала в T-разряд «1», что бы произошло? Работа программы сейчас же прервалась бы по вектору @# 14, а из его второго слова было бы переписано ССП с нулевым T-разрядом. Но даже если занести и туда код 20, всё равно ничего хорошего не получится - нам нужно прерывание не после записи T-разряда, а после выполнения очередной команды отлаживаемой программы. Нужно найти другой путь, чтобы, как только будет установлен T-разряд, сразу начала выполняться очередная команда. А как только она будет выполнена - произойдёт прерывание по вектору @#14, и мы сможем узнать о только что выполненной команде то, что хотим. Как это сделать? Как записать T-разряд в нужный момент, «на самом краю» запуска очередной команды?
Вспомним, как выполняется возврат из прерывания. Из стека восстанавливается сначала PC, а затем - ССП: то значение PS, которое было занесено в стек при переходе к обработке прерывания, пишется обратно в регистр PS. А потом? Потом начинается выполнение следующей команды программы... Ага, кажется, именно это нам и нужно! Что если перед возвратом из прерывания «подменить» сохранение в стеке значение ССП на такое, в котором установлен T-разряд? Процессор его «скушает», не подозревая «подвоха», и выполнит очередную команду, после чего проверит T-разряд и обнаружит, что там «1». Ему ничего не останется, кроме как выполнить прерывание по вектору @#14. А тут-то мы его и будем ждать со своей программой, чтобы «подсмотреть» то, что нам нужно... Кажется, путь найден! Но сначала нужно организовать прерывание, чтобы при выходе из него «обмануть» ЦП. По какой команде это сделать? В принципе, можно по любой, лишь бы это было именно командное прерывание. Но в наборе команд есть специальная операция BPT, которая вызывает прерывание по тому же вектору @#14 (но не отладочное по T-разряду, а просто командное, на манер EMT). Воспользуемся ею и не будем трогать другие векторы. А что мы будем делать во время «отладки»? Что хотим «подсмотреть»? Прежде всего, нам нужно определить, какая команда выполняется, а для этого желательно узнать её адрес и код (пока мы не будем замахиваться на то, чтобы дизассемблировать команды - переводить их на язык ассемблера, - это чересчур сложно). И пусть нас ещё интересует, к примеру, содержимое одного из регистров - R1. А затем напишем какую-нибудь программку, которая будет работать именно с R1, и посмотрим, что получится. Желательно также иметь хотя бы минимальный «сервис» - хоть как-то маркировать выводимые данные, чтобы можно было понять, где адрес, где код команды, а где - содержимое регистра. А как переходить к следующей команде? Пусть для простоты «отлаживаемая» программа выполняется тогда, когда нажата любая клавиша, а если клавишу отпустить, можно просмотреть данные, выдаваемые «отладчиком». Итак, «техническое задание» на программный продукт готово. Осталось написать программу, а «что нам стоит дом построить», да ещё из такого податливого «материала» как операторы ассемблера? Вот наш простейший «отладчик» и готов:
; Запуск отладчика MOV #BPT1,@#14 ; Записать пусковой вектор @#14 BPT ; Прерывание по вектору @#14 ; Демонстрационная программа MOV #1,R1 ; Записать в R1 единицу 0: ASL R1 ; Сдвиг влево R1 ADC R1 ; Прибавить перенос BR 0 ; Программа зациклена ; Трассировка демонстрационной программы TRS: MOV R0,-(SP) ; Сохранить в стеке MOV R3,-(SP) ; регистры, MOV R4,-(SP) ; используемые MOV R5,-(SP) ; в "отладчике" MOV 'A',R0 ; Сообщение "адрес" EMT 16 ; выдать MOV R0 ; на EMT 16 ; экран MOV 10(SP),R3 ; Извлечь из стека PC в R3 MOV R3,R4 ; Печать адреса CALL @#163220 ; команды MOV 'K',R0 ; Сообщение "КОД" EMT 16 ; выдать MOV '=',R0 ; на EMT 16 ; экран MOV @R3,R4 ; Печать кода CALL @#163220 ; команды MOV 'R',R0 ; Сообщение "регистр" EMT 16 ; выдать MOV R0 ; на EMT 16 ; экран MOV R1,R4 ; Печать CALL @#163220 ; содержимого R1 MOV #12,R0 ; Перевод EMT 16 ; строки 0: BIT #100,@#177716 ; Клавиша нажата? BNE 0 ; Нет - ждать, иначе продолжать MOV (SP)+,R5 ; Восстановить MOV (SP)+,R4 ; регистры MOV (SP)+,R3 ; из MOV (SP)+,R0 ; стека ; Запись T-разряда, выход из прерывания BPT1: BIS #20,2(SP) ; Занести T-разряд в ССП в стеке MOV #TRS,@#14 ; Записать рабочий вектор @#14 RTT ; Выход из прерывания END
Разберёмся с некоторыми деталями. Сначала мы пишем в качестве вектора @#14 адрес метки BPT1 и даём прерывание BPT. Происходит переход к метке BPT1, а там мы заносим в ССП, хранящееся в стеке (оно на 2 выше вершины, ведь мы в прерывании!), единицу в T-разряд. Затем записываем новый вектор @#14 («рабочий», т.е. тот, по которому будет передаваться управление при прерывании по T-разряду), после чего следует команда возврата из прерывания, но не RTI, a RTT. Она незначительно отличается от RTI (мы уже упоминали, чем именно: после её выполнения не происходит прерывания по T-разряду) и применяется для выхода из отладочного прерывания.
Теперь запускается наша демонстрационная программа. Она зациклена и делает следующее: сначала в R1 пишется 1. затем R1 сдвигается влево. Когда «1» достигнет старшего разряда R1, возникает перенос, но бит переноса каждый раз прибавляется к R1, поэтому единица снова попадёт в младший разряд, опять начнёт сдвигаться, и так до бесконечности.
После выполнения каждой команды происходит прерывание по T-разряду (ведь наш «отладчик» включён!) и управление передаётся на метку TRS - «Трассировка».
Вот теперь в работе отладчик. Прежде всего, трассировки в нем самом уже нет - ведь после прерывания в ССП переписалось новое значение из адреса @#16 - второго слова вектора. Мы не знаем, что там было, но в данном случае это неважно: T-разряд там равен нулю, будьте уверены! И отлично, иначе отладчик не смог бы работать, а прерывал бы сам себя после каждой команды. В отладчике мы будем использовать часть регистров, а чтобы не возникло потом неприятностей, сохраним их в стеке (в данном случае это не важно, но для других отлаживаемых программ пригодится). Сначала будем выдавать адрес команды. Даём сообщение «А=» - это наш «сервис», а потом. Вспомним, что у нас сейчас хранится в стеке. По порядку туда было записано: ССП, PC, R0, R3, R4, R5. Ага, PC нам и надо, ведь там адрес. Но какой? Адрес следующей команды. Что же, для простоты давайте индицировать как раз следующую команду, это даже удобнее - мы сначала посмотрим, что «там, за поворотом», а потом уже разрешим ей работать (кстати, именно так обычно и делается в настоящих отладчиках). Итак, PC в стеке на 8 байт от вершины (после него в стек мы «загнали» ещё 4 регистра). Применяем косвенную индексацию: 10(SP). Мы «достали» значение PC, теперь надо его выдать на экран. Но нам он ещё пригодится, поэтому не пожалеем для него отдельного регистра R3. А как выдать его на экран? В МСД есть подпрограмма, которая делает то, что нам нужно, - выдаёт на экран содержимое регистра R4. Чтобы не писать её заново, просто обратимся к ней по адресу @#163220. Она не сохраняет регистры R0, R4 и R5, вот почему мы их оставили в стеке вместе с R3. Итак, число (значение PC) сначала копируется в R4, потом выводится на экран. Готово, поехали дальше! Сервис «К=», это будет код команды. Вот теперь нам пригодится R3, ведь там адрес, а по этому адресу - сама команда. Словом, косвенное обращение через регистр. В R4 его и на экран! Ну, а теперь совсем просто: содержимое R1, которое мы хотим «подсмотреть», гоним туда же, но сначала опять сервис: «R=». Выведена строка параметров, адрес, код команды, содержимое R1. Чтобы не запутаться, переведём строку. Что мы ещё хотели? Ах, да! Если клавиша нажата - программа работает, если нет - ждёт. Регистр @#177716 дозволяет решить и эту задачу. Вот мы нажали любую клавишу, и программа пошла дальше: восстановлены регистры, и... А дальше мы уже были - снова заносим вектор @#14 и T-разряд (можно бы этого и не делать, но лучше перестраховаться) и возвращаемся из прерывания. Вперёд, нас ждёт следующая команда!
Выглядит не слишком сложно, не правда ли? А ведь наша программа имеет практически все основные черты настоящего отладчика. Конечно, есть масса деталей, которых мы ещё не учли. Например, если команда отлаживаемой программы состоит из двух или трёх слов, то будет выдано только её первое слово. А если в отлаживаемой программе встретится EMT 14, то перепишутся все векторы (и 14-й в том числе), и прощай отладочный режим! И не всё, что надо, мы видим на экране, и нет точки останова, и нет дизассемблера, и наш отладчик неперемещаемый, и... Ну ясно, а что же вы хотели? Чтобы, едва освоив ассемблер, сделать настоящий отладчик? Но главное - понять, как он работает, и эта цель достигнута. А дальше дело за вами.
Но прежде чем окончательно проститься с вектором @#14, рассмотрим ещё один вопрос. При пользовании отладчиком (простейшим или самым сложным) любое командное прерывание, будь то EMT, TRAP, IOT или другое, отрабатывается в пошаговом режиме как одна команда. Это естественно - ведь при обращении к прерыванию переписывается ССП и T-разряд сбрасывается, а с ним на время обработки прерывания отключается и трассировочный режим. Поэтому мы видим на экране просто «EMT 32» или «TRAP 114», не более. Но мы-то теперь знаем, что за такой скромной «вывеской» могут скрываться сотни команд... Как бы их «подсмотреть», увидеть, как работает EMT-диспетчер, где находится и что делает программа обработки команды IOT? Неужели ничего нельзя сделать? Можно. Достаточно переписать второе слово интересующего нас вектора прерывания, занеся туда T-разряд, и прерывание это станет «видимым» для отладчика, т.е. и внутри него будет обеспечиваться пошаговый режим и индикация всего, что нас интересует.
★ ★ ★
Следующий по порядку вектор прерывания - @#20. Прерывание по нему возникает по команде IOT и аналогично командным прерываниям BPT, EMT и TRAP, только, в отличие от двух последних, не имеет аргументов. Оно может быть использовано для выполнения каких-нибудь действий. Допустим, нам нужно подать в определённый момент звуковой сигнал. В начале программы запишем: MOV #IOT,@#20 - это мы переписали вектор @#20. Напишем теперь программу обработки прерывания:
IOT: MOV #7,R0 ; Код звукового сигнала MOV #100,R1 ; 100 повторов (64д) 0: EMT 16 ; Выдать звук (щелчок) SOB R1,0 ; Цикл по R1 RTI ; Выход из прерывания
Теперь в любой момент можно вызвать звуковой сигнал одной командой IOT. С равным успехом она же может применяться и для любых других целей. Вообще же команда IOT предназначена для вызова процедур и обработки ошибок в операционных системах, и по этой причине её лучше не трогать «по мелочам», иначе при работе ваших программ с дисководом (если он у вас имеется) могут возникнуть проблемы. Впрочем, то же может быть отнесено к командам TRAP, EMT, BPT, к использованию свободных ячеек в системной области... Вообще, лучше ничего не трогать! И не писать программы - тогда и проблем не будет... А если серьёзно, то в каждом конкретном случае нужно точно знать, что «трогать», а что - нет. (Обычно такие сведения - об используемых ячейках и ограничениях на изменение их содержимого - приводятся в документации к операционным системам, отладчикам, мониторам и т.д. - Прим. ред.)
★ ★ ★
Вектор @#24 для нас практически бесполезен. так как на БК-0010 он не имеет так называемой аппаратной поддержки. Но можно убедиться, что программа его обработки в ПЗУ имеется. Дайте в МСД команду 160714G - и вы получите ответ «сбой питания». Ну а если всё-таки вспомнить, что прерывание по вектору @#24 возникает при подаче сигнала на вход ACLO ЦП? Если не побояться залезть в БК с паяльником? Да, кое-чего мы можем достигнуть. Если подать на вывод 13 микросхемы D6.4 (К561ЛЕ5) положительный импульс, то произойдёт прерывание по вектору @#24! Подать такой импульс можно, если сначала зарядить конденсатор небольшой ёмкости (0.03...0.05 мкФ) до напряжения +5 В, а затем разрядить его на вывод 13 D6.4 (есть, впрочем, и другие способы). Чего мы достигнем? Прерывание ACLO - внеприоритетное, аналогично клавише «СТОП», и, переписав вектор @#24, мы можем нажатием дополнительной кнопки обратиться по этому вектору. А там, например, может быть программа графического копирования экрана. Или программа инициализации принтера. Или что-нибудь ещё... Но тут пора сказать, что автор категорически не рекомендует лезть в БК с паяльником тем читателям, которые берут его в руки не чаще, чем раз в месяц, да и то, чтобы напаять «жучок» на предохранитель. И никакой ответственности за их действия автор (как и редакция) на себя брать не собирается.
★ ★ ★
Теперь рассмотрим последний из оставшихся векторов прерывания - @#100. Это, как уже было сказано, прерывание по таймеру. Таймер имеется в виду внешний - это подключаемое к БК устройство, выдающее импульсы строго определённой частоты, например 10 импульсов в секунду. Импульсы, подаваемые на вход таймера (контакт В1 порта ввода-вывода), должны иметь уровень ТТЛ-логики («0», или низкий уровень - от 0 до 0.4 В; «1», или высокий уровень - от 2.4 до 5 В). При прохождении на вход таймера заднего фронта импульса (т.е. когда «единица» сменяется «нулём») возникает прерывание по вектору @#100 (если, конечно, его разрешает приоритет ЦП). Как можно использовать этот вектор? Можно по прямому назначению, для отсчёта времени. Но, кроме того, это дополнительный канал связи БК с внешним миром, и его применения могут быть различными. Если, допустим, у вас есть магнитофон, в котором имеется счётчик, выдающий импульсы при вращении кассеты, можно подать эти импульсы (конечно, преобразовав их к нужному уровню!) на вход таймера и, подсчитывая их, получить программный счётчик расхода ленты. Правда, тут есть одна неприятность - при записи на магнитофон или чтении прерывания должны быть запрещены, иначе информация будет искажена. Но можно сделать счётчик, работающий только при перемотке, и таким образом автоматизировать хотя бы поиск файлов (разумеется, для этого магнитофон должен иметь полное дистанционное управление, и вообще это не так просто). А можно подключить ко входу таймера, например, аналого-цифровой преобразователь (АЦП) типа ПНЧ - преобразователь напряжения в частоту и, пользуясь системным (встроенным) таймером, измерять число поступивших импульсов в единицу времени, а затем вычислять измеренное напряжение (или иной параметр) Или можно сделать так, чтобы ЭВМ при поступлении на вход В1 сигнала считывала информацию с внешних устройств (например, с микрокалькулятора или с цифрового измерительного прибора) или, наоборот, выдавала информацию на порт ввода-вывода либо на ТЛГ-канал. Приведём элементарный пример обработки прерывания по вектору @#100 [2].
; Инициализация программы CLR R1 ; Очистить счётчик MOV #ТIМ,@#100 ; Записать вектор @#100 HALT ; Конец ; Счёт импульсов с индикацией TIM: INC R1 ; Счётчик увеличить на 1 MOV R1,R4 ; Выдать текущие CALL @#163220 ; показания на экран RTI ; Возврат из прерывания END
Разумеется, в зависимости от наших целей программа обработки прерывания может быть и другой. Максимальная частота подаваемых на вход таймера импульсов ограничена временем, затрачиваемым на обработку каждого прерывания, и может при простейших подпрограммах обработки достигать 20-30 кГц. Только надо учитывать, что при этом скорость обработки компьютером текущей задачи резко снижается, ибо почти всё время уходит на обработку прерываний.
Контрольные вопросы и задания
- Придумайте способ, который бы позволил обращаться с помощью команды
IOT не к одной программе обработки, а к нескольким,
как EMT.
-
Нужно после IOT записать в программу аргумент, например, так:
IOT .#122
Программа же обработки прерывания должна содержать своеобразный диспетчер, который прочитает аргумент из памяти, выполнит по нему нужную программу, а перед возвратом из прерывания увеличит в стеке содержимое PC на 2, чтобы «обойти» аргумент и не передать на него управление. Про то, что команду IOT лучше вообще не трогать, уже говорилось, но это не причина чтобы лишить читателя удовольствия поломать голову.
-
- Каково «нормальное» содержимое вектора прерывания
@#4 в ПМ, МСД, Фокале и Бейсике? Предложите
способы, которые бы позволили это узнать.
-
Содержимое вектора @#4: 100442, 160722, 121110, 120234. Узнать его можно, если прочитать ячейку @#4. В МСД это делается непосредственно, в Бейсике и Фокале - с помощью средств этих языков (функции PEEK и FX соответственно), а в ПМ придётся написать и запустить программу:
MOV @#4,R4 CALL @#163220 HALT END
-
А что же векторы прерываний, «жизненно необходимые» самой ЭВМ, - прерывания от клавиатуры @#60 и @#274, а также уже не раз рассмотренные векторы командных прерываний @#30 и @#34? Не забыл ли о них автор? Нет, просто эти векторы недостаточно рассмотреть на отдельных примерах, как другие. Работа с ними может привести к таким последствиям, к таким ошеломляющим на первый взгляд возможностям и результатам, что их лучше оставить для отдельной темы, к рассмотрению которой мы теперь и переходим.
Концепция системных драйверов
Жил-был... нет, не серенький козлик. И не у бабушки. Был у пользователя редактор текста. Неплохой, в общем, редактор EDASP. И был к нему принтер, для которого в редакторе был драйвер. И всё было хорошо, пока пользователю не встретился новый редактор, скажем МИКРО.10К. Он ему понравился, но вот беда - в этом редакторе нет драйвера принтера! Что делать - лезть в МИКРО.10К и пытаться «вставить» в него драйвер? На этот «подвиг», прямо скажем, способны немногие. Перекодировать тексты, написанные в МИКРО (благо программы «конверторов» текста есть), и печатать через EDASP? Ужасно неудобно. А если завтра понравится ещё один редактор? Все проблемы решать заново?
Или вот на БК-0010 есть язык Фокал. Многие его любят, несмотря на низкую скорость и точность, - он очень гибок. (Попробуйте-ка в Бейсике обратиться к строке, задав её номер в виде переменной или выражения, - а в Фокале это самая заурядная процедура. Или попытайтесь записать на МЛ саму программу программным путём, а не в диалоговом режиме. А Фокал это может!) К тому же этот язык очень экономно расходует память. Например, на Фокале есть программа, которая... сочиняет рассказы о Штирлице! (На полном серьёзе, автор нисколько не шутит.) И причём довольно смешные рассказы! О возможности таких программ (но только о возможности!) пишет Д. Кнут в своей знаменитой трёхтомной книге «Искусство программирования на ЭВМ», а на БК - пожалуйста, вот она, эта программа. Вот бы распечатать «сочинённый» рассказ на бумаге! Но увы, в Фокале не предусмотрено выхода на принтер...
Или у вас есть принтер и есть Бейсик. В Бейсике, конечно, есть специальные операторы для вывода на печать. Но, к сожалению, ваш принтер и Бейсик взаимно несовместимы - формат выдачи Бейсика «не нравится» принтеру или наоборот. Причём принтер этот, как ни странно, работает с некоторыми редакторами текста, но, как назло, с теми, с которыми не любите работать вы... Что делать - покупать за большие деньги электронную «приставку» к принтеру для перевода его протокола обмена в стандартный формат? А для редактора текста ещё одну приставку? А для того, чтобы распечатать красивую заставку к игре, что купить? Фотоаппарат?!
Или вот вы живете в «некотором царстве» (попросту говоря, в республике). Сейчас все вдруг вспомнили, что раз республика суверенная, то надо пользоваться национальным языком. Но вот беда - в школах, институтах и прочих заведениях «компьютерное население» сплошь «русскоязычное» (ну, конечно, ещё и англоязычное, но ведь мы не в Лондоне или Брайтоне!). Как научить компьютер работать с национальным алфавитом, буквы которого зачастую отличаются от русского и английского по начертанию? Вы скажете, есть специальные редакторы текста, в которых предусмотрен спецалфавит? Но как быть с Бейсиком и Фокалом? Писать текстовые сообщения в программах по-русски? Или не использовать компьютер для этих целей?
Вот какие могут быть разные задачи. А решаются они, как ни странно, в принципе, всё одинаково - с помощью системных драйверов. Что же это такое - драйвер и чем отличается системный драйвер от обычного?
Драйвер (от английского «driver» - «водитель») - это программа, управляющая другими программами или взаимодействующая с внешними устройствами ЭВМ. А что мы понимаем под системным драйвером?
Пусть нам нужно ввести в редактор текста возможность печати на принтере. Обычный путь таков: мы включаем в редактор дополнительную директиву, например «печать от метки до курсора», и по ней редактор, вместо того чтобы выдать, как обычно, текст на экран, выдаёт заданную часть текста в виде символьного кода на принтер (соблюдая формат обмена с принтером, разумеется). Но возможен и другой путь. Как выводится информация в редакторе текста на экран? Очевидно, через EMT 16 (может быть, и через EMT 20, но это менее вероятно). Заменим вектор @#30 и напишем свою программу его обработки («супервизор»):
; Инициализация драйвера MOV PC,R0 ; Вычислить адрес супервизора ADD (PC)+,R0 ; в перемещаемом .@EMT+2 ; формате MOV R0,@#30 ; Записать новый вектор @#30 HALT ; Стоп ; Супервизорная программа EMT: MOV R5,-(SP) ; Сохранить R5 в стеке MOV 2(SP),R5 ; Адрес следующей команды CMP #104016,-(R5) ; Текущая команда - EMT 16? BNE 3 ; Нет - выход MOV R0,-(SP) ; Сохранить R0 1: BIT #400,@#177714 ; Принтер готов? BEQ 1 ; Нет - ждать BIS #400,R0 ; Строб-бит MOV R0,@#177714 ; Код символа и строб - в порт 2: BIT #400,@#177714 ; Код принят? BNE 2 ; Нет - ждать BIC #400,R0 ; Очистить строб-бит MOV R0,@#177714 ; Код в порт MOV (SP)+,R0 ; Восстановить R0 3: MOV (SP)+,R5 ; Восстановить R5 JMP @#100112 ; Выход в EMT-диспетчер
Что делает эта программа? Сначала она проверяет, какая команда вызвала прерывание по вектору @#30. Если это EMT 16, то надо с ней работать. А если другая - EMT 20, EMT 22, EMT 36? Тогда просто восстановим регистр R5, который использовался нами для служебных целей, и скатертью дорога в стандартный EMT-диспетчер БК-0010, пусть он сам с ней разбирается. А как опознать EMT 16? Конечно, по её коду - 104016. А как найти код? Очень просто - мы в прерывании, адрес следующей команды - в стеке (за 2 байта от вершины), а код предыдущей - в памяти, за 2 от следующей...
Когда же команда EMT 16 опознана, мы вспоминаем, что у нас сейчас в младшем байте R0 записан код символа, выводимого на экран. А мы вместо экрана выдадим его на принтер! Понятно, что надо соблюдать протокол обмена - сначала дождаться сигнала готовности принтера (стандартно он подключён к разряду 08 порта ввода), затем записать строб-бит (сигнал принтеру: «Печатай!», он подаётся на разряд 08 порта вывода) и сразу же выдать на восемь младших разрядов порта вывода код символа. Теперь - внимание! Принтер должен принять код и начать его печатать. При этом он снимает сигнал готовности, сигнализируя, что занят другим делом и не может принять следующий код. Дождёмся этого момента и снимем бит стробирования, теперь уж ясно, что принтер код принял и нам можно уходить восвояси за следующим кодом. А «чтобы чего не вышло», мы при входе в программу сохраняем регистр R0, а потом его восстанавливаем - ведь в его старшем байте, в принципе, тоже могла быть информация, которую мы испортили строб-битом. Теперь всё в порядке, «словно ничего и не было», и можно передать управление в EMT-диспетчер, пусть он выведет символ на экран как обычно. Вот и всё.
Конечно, программа печати и протокол обмена с принтером заведомо выбраны самые примитивные. Реальный принтер может принимать данные в прямом коде (а порт у нас - инвертирующий), тогда придётся ввести команду COM R0. Или может потребоваться не одновременная подача строба и данных, а последовательная - сначала одно, потом другое. Вполне возможно также, что кодовая таблица принтера не совпадает с принятой на БК-0010 частично или полностью, тогда до подачи кода символа на принтер его надо перекодировать - привести в соответствие с кодом, требуемым принтеру. Но всё это зависит от конкретного принтера и суть дела не меняет. А суть эта в том, что мы, не меняя программу пользователя, «сели» на неё своим драйвером и выводим информацию совсем не туда (или не только туда), куда она должна была выводиться. Это и есть (в нашем определении) системный драйвер, иначе называемый супервизорным драйвером или просто супервизором. (Для справки: супервизорная программа - машинная программа, являющаяся частью операционной системы, которая управляет выполнением других машинных программ и регулирует поток работ в системе управления данными.)
А что если в программе пользователя имеется, например, команда EMT 14? Она ведь перепишет все векторы прерывания и тем самым «сбросит» супервизорный драйвер с системы. Нам нетрудно такую команду «поймать» до исполнения, ведь код её известен - 104014 - и мы можем проверить его, как и 104016. Но как быть дальше? Если мы её просто проигнорируем, то тем самым можем нарушить работу программы пользователя - ведь EMT 14 там появилась, наверное, не зря! Надо её выполнить, но так, чтобы «не выпустить вожжи из рук». Сделаем это, переписав нашу программу начиная с метки «3»:
3: CMP #104014,@R5 ; Текущая команда - EMT 14? BNE 4 ; Нет - выход в EMT-диспетчер MOV @#30,-(SP) ; Сохранить вектор @#30 в стеке MOV #100112,@#30 ; Записать "нормальный" вектор @#30 EMT 14 ; Исполнить EMT 14 MOV (SP)+,@#30 ; Восстановить вектор @#30 MOV (SP)+,R5 ; Восстановить R5 RTI ; Выход из прерывания 4: MOV (SP)+,R5 ; Восстановить R5 JMP @#100112 ; Выход в EMT-диспетчер
Вот так! Сначала восстановили обычный вектор @#30 (иначе по EMT 14 система снова выйдет на супервизор), а «супервизорный» вектор сохранили в стеке, ведь EMT 14 стек «не трогает». Потом выполнили EMT 14, и теперь можно восстановить «наш» вектор @#30 из стека. Атака отбита, «сбросить» драйвер программе пользователя не удалось! Но изменилась наша программа и в другом - мы уже не можем передать управление в EMT-диспетчер, ведь тогда он ещё раз выполнит EMT 14, которое мы только что «обезвредили» так что закончим прерывание сами командой RTI[3].
Теперь уже легче сообразить, как можно печатать на принтере не только по EMT 16, но и по EMT 20, - вспомним, какая должна быть исходная информация для этого прерывания.
В R1 - адрес начала строки текста, в R2 - признаки конца: длина строки или код последнего её символа (а может быть, и то и другое). Конечно, это задача посложнее - надо не только вывести строку на принтер, но и выдать её посимвольно на экран, а потом привести регистры R1 и R2 в такое состояние, как будто команда EMT 20 сработала без всяких фокусов. Выдавать символы придётся через EMT 16. а весь этот процесс будет называться эмуляцией команды EMT 20. Мы не приводим соответствующую программу, а предоставляем сделать это читателю - ведь наша книга не справочник программ, её задача - научить читателя самостоятельно эти программы писать. Привести готовые программы драйверов, эмуляторов и т.п. было бы куда как просто!
А как быть, если наш драйвер должен работать с языком высокого уровня, например с Фокалом? Придётся в него ввести «повторный выход» в Фокал. Вернитесь в раздел, посвящённый МСД.
Там сказано, как выйти в Фокал без стирания ОЗУ. Если вы запомнили, что делают директивы МСД, вам не составит труда написать такую же программу на ассемблере. А чтобы в Фокале можно было нормально работать в присутствии драйвера, нужно драйвер защитить - сделать недоступной для Фокала зону памяти, где он будет размещён. Для этого достаточно перед выходом в Фокал записать адрес начала драйвера в ячейку @#1744 - эта ячейка в Фокале «отвечает» за зону ОЗУ, доступную интерпретатору. Естественно, найти адрес начала нужно с помощью псевдооператора «.@», чтобы не нарушить условия перемещаемости нашего драйвера. Вот теперь всё. Разве что надо будет ввести ещё запрос выхода из инсталлятора драйвера. Если в ПМ или МСД, то в системную область не нужно ничего писать, как это делается для выхода в Фокал, чтобы ничего не напортить. Тогда одна и та же программа-драйвер будет одинаково успешно работать в Фокале и в кодах, например в МСД.
А Бейсик? С ним всё так же, только надо знать, что он выдаёт свои тексты (всё, кроме сообщения «Ок.») не по EMT 16, а по EMT 20, так что без эмуляции этой команды не обойтись. А специальный выход в Бейсик не обязателен - можно ведь загрузить и запустить драйвер прямо в Бейсике, определив его как пользовательскую функцию в кодах, а затем к ней обратиться, например, так (драйвер размещён по адресу 30000):
DEF USR=&O30000 ? USR(A)
После этого достаточно дать команду на выход в коды - Бейсик выдаст, конечно, сообщение «СТОП» и будет работать как ни в чём не бывало дальше, но теперь всё, что выдаётся на экране, будет печататься и на принтере.
Но постойте! Ведь это совсем не то, что надо! Нам нужно печатать не всё, а только часть, и не всегда, а только тогда, когда это нужно! Как же быть? Мы этого, кажется, не учли? Ну что же, учтём. Ведь у нас много «свободных» кодов, а код символа в нашей программе всегда можно проверить. Введём какую-нибудь ячейку-«переключатель» (например, «на самом дне» стека по адресу @#374) и будем делать следующее:
- инициализируя драйвер, всегда сбрасываем ячейку,
- символ на печать выдаём только тогда, когда в ячейке не нуль,
- независимо от того, печатается ли символ код его всегда проверяем, и если он равен «ключу», инвертируем нашу ячейку-«переключатель».
Понятно? Пусть наш «ключевой» код - 1, что соответствует клавише «СУ/А» или команде Бейсика ?CHR$(1) (а также команде Фокала X FCHR(1)). Мы всегда можем выдать этот код как в диалоговом режиме, так и в программном. Содержимое ячейки @#374 проинвертируется, станет не нулевым, и символы будут выводиться не только на экран, но и на принтер. А если нам это не нужно - снова дадим тот же ключ, содержимое ячейки станет равным нулю и печать прекратится. (Каждый может придумать такой порядок управления печатью, какой ему больше нравится, и написать программу тоже по своему вкусу. А автор приводить полную программу, как уже было сказано, не собирается, так как считает, что в этом случае незачем было писать книгу - различных драйверов сегодня тиражируется достаточно.)
Вот теперь наш драйвер как будто полностью готов и можно с ним работать. Каковы его слабые места? Первое - это то, что супервизорная программа может быть «затёрта» данными или текстом программы пользователя (ЭВМ тогда «зависнет»), поэтому о её присутствии в ОЗУ программист всегда должен помнить. А второе - программа пользователя всё-таки может «сбросить» супервизорный драйвер, отключить его, если она перепишет вектор @#30 напрямую, без всякого EMT 14 - тут уж ничего не поделаешь. К счастью, программ, изменяющих вектор @#30, не так уж много.
А как быть, если нам нужна графическая копия экрана? Нужно написать специальную подпрограмму графического копирования. Мы могли бы её привести, но ведь почти для каждого принтера она будет своя, отличная от других, да это и не входит в нашу задачу. В общих чертах подпрограмма эта будет выглядеть так. Сначала нужно перевести принтер в графический режим (в соответствии с его системой команд), а затем выдавать на него и печатать по нескольку (в соответствии с его же графическим форматом) точек экранного ОЗУ. Достигнув конца графической строки экрана, надо перейти к следующей и т.д., а распечатав весь экран - выйти из подпрограммы. Можно построить графическую печать и таким образом, чтобы копия экрана выводилась как в «нормальном» положении, так и с поворотом на 90° - каждый способ имеет свои преимущества. А обращаться к данной подпрограмме можно из того же драйвера, только по иной команде-ключу, например по коду 2.
Здесь есть ещё одна существенная деталь. Если вы хотите графически копировать экран «из любого исходного положения», то следует помнить, что он в БК-0010 организован по типу рулона, и его начало в каждом конкретном случае может быть с любого адреса. Определить адрес начала экрана нетрудно - в этом может помочь, например, регистр @#177664 или, ещё лучше, ячейка @#204. Но если пытаться распечатывать фрагмент ОЗУ с начала экрана и длиной 40000, то мы рано или поздно «выскочим» в область ПЗУ (если только адрес начала рулона также не равен 40000). Нужно своевременно зафиксировать этот момент и скорректировать адрес, перейдя от конца ОЗУ экрана к его началу. Похоже, это довольно сложная задача и придётся-таки привести пример листинга. Программа будет рассчитана на довольно распространённый принтер типа МС-6312, на его систему команд и на его формат графического вывода. Копироваться экран будет в «нормальном» положении, т.е. горизонтально на листе бумаги, а с целью увеличения размеров изображения и компенсации искажений, возникающих при переходе от формата экрана к формату принтера, высота по вертикали «удваивается» - на данном типе принтера это даёт наиболее приемлемые результаты. Кроме того, такой вариант программы можно считать самым сложным из всех возможных, и если вы разберётесь, как работает эта подпрограмма, то сможете написать аналогичную и для другого типа принтера. Понять же, как она работает, вам помогут предшествующие разъяснения и построчные комментарии. Формат подпрограммы - перемещаемый, она сопровождается двумя вспомогательными подпрограммами и двумя строками команд принтера. Система команд МС-6312 близка к EPSON-стандарту. Для тех, кто не знаком с описанием этого принтера поясним коды команд:
- строка К1 - 30,33,100,7, что означает:
30 - отмена строки, т.е. сброс «мусора» (CAN);
33 - регистровый код (ESCAPE, ESC);
100 - «@», ESC-код инициализации принтера;
7 - признак конца командной последовательности;
- строка К2 - 33,63,30,12,33,113,0,2,7:
33 - ESC;
63 - символ «3», ESC-код выбора N/216д-дюймового интервала между строками;
30 - число N в обозначении «N/216д- дюймовый интервал», т.е. устанавливается интервал 24д/216д;
12 - перевод строки (LF);
33 - ESC;
113 - символ «K» (лат), выбор графического режима одинарной плотности;
0, 2 - количество точек в графической строке режима «K», что значит (0 + 256д * 2) = 512д точек, это формат экрана БК-0010,
7 - признак конца командной последовательности.
А теперь сама подпрограмма [4]:
; Подпрограмма графического копирования экрана GRK: JSR R4,@#110346 ; Сохранить MOV R5,-(SP) ; регистры CALL КМ1 ; Инициализация принтера MOV @#204,R1 ; Адрес ADD #36000,R1 ; начала экрана MOV #100,R5 ; Цикл из 64д строк по 4 точки 0: CALL КМ2 ; Перевод строки на 24д/216д дюйма MOV #100,R4 ; Цикл из 64д знакомест 1: MOV #10,R3 ; Цикл из 8 столбцов точек 2: MOV #4,R2 ; Цикл из 4 вертикальных точек CLR R0 ; Очистить буфер граф.элемента 3: ASLB R0 ; Сдвиг буфера ASLB R0 ; на 2 точки RORB @R1 ; Очередная точка = 0 ? BCC 4 ; Да - следующая точка BISB #3,R0 ; Иначе - 2 точки в буфер BISB #200,@R1 ; Восстановить точку на экране 4: ADD #100,R1 ; Следующая точка по вертикали BPL 5 ; в пределах экрана ? SUB #40000,R1 ; Нет - коррекция "на рулон" 5: SOB R2,3 ; Цикл из 4 вертикальных точек CALL TYP ; Выдать графический элемент на печать SUB #400,R1 ; Адрес прежний - начало столбца CMP #40000,R1 ; В пределах экрана ? BLOS 6 ; Да, иначе ADD #40000,R1 ; коррекция 6: SOB R3,2 ; Цикл из 8 столбцов точек INC R1 ; Следующий байт SOB R4,1 ; Цикл из 64д знакомест (байт) ADD #300,R1 ; Следующая строка печати BPL 7 ; в пределах экрана ? SUB #40000,R1 ; Нет - коррекция 7: SOB R5,0 ; Цикл строк MOV #12,R0 ; Перевод CALL TYP ; строки CALL КМ1 ; Инициализация принтера MOV (SP)+,R5 ; Восстановить JSR R4,@#110362 ; регистры RET ; Выход ; Подпрограмма выдачи кодов на принтер TYP: JSR R4,@#110346 ; Сохранить регистры MOV #177714,R1 ; Адрес порта BIC #177400,R0 ; Выделить младший байт 1: BIT #400,@R1 ; Принтер готов? BEQ 1 ; Нет - ждать CLR @R1 ; Очистка порта BIS #400,R0 ; Строб - бит MOV R0,@R1 ; Код и строб в порт 2: BIT #400,@R1 ; Код принят? BNE 2 ; Нет - ждать JSR R4,@#110362 ; Восстановить регистры RET ; Выход ; Подпрограмма выдачи команд на принтер КМ1: MOV PC,R3 ; Команды: "сброс", ADD (PC)+,R3 ; "инициализация" .@К1+2 BR КОМ ; Исполнение КМ2: MOV PC,R3 ; Команды: "графика", "перевод ADD (PC)+,R3 ; строки на 24д/216д дюйма" .@К2+2 КОМ: MOVB (R3)+,R0 ; Очередной код команды CMPB #7,R0 ; Если 7 - BEQ 1 ; конец CALL TYP ; Передать код на принтер BR КОМ ; Следующий код 1: RET ; Выход ; Командные коды принтера: ; сброс строки, инициализация принтера К1: .B:30,33,100,7 ; перевод строки на 24д/216д дюйма, задание граф.режима К2: .B:33,63,30,12,33,113,0,2,7.E
Вот теперь, чтобы получить копию экрана, вам уже не понадобится фотоаппарат...
★ ★ ★
А что ещё можно сделать с помощью вектора @#30? Допустим, вы изобрели свой формат записи на МЛ, который даёт, например, в 4 раза более высокую плотность записи и в 3 раза более высокую надёжность чтения (заметим, что такой формат на БК-0010 уже есть[5]). Этого ещё мало для успеха! Что можно сделать с помощью спецкопировщика? Только организовать надёжный и компактный архив. Вы не сможете загружать в таком формате ни тексты для редакторов, ни программы на Бейсике и Фокале - придётся каждый раз вначале «распаковывать» файлы, т.е. перезаписывать их в стандартном формате БК-0010. Есть ли выход? Да, это опять супервизорный драйвер. Пусть он перехватывает прерывание по вектору @#30, но на этот раз выделяет EMT 36. Затем надо обратиться к блоку параметров EMT 36 и проанализировать его в зависимости от результатов анализа выполняется чтение или запись, но только с помощью не стандартного драйвера магнитофона БК, а своего спецдрайвера. Ведь в блоке параметров для EMT 36 мы найдём всё: и команду (запись или чтение), и адрес, и длину файла... А после выполнения заданной операции с требуемым файлом нужно установить все системные ячейки ЭВМ, стек и регистры в такое состояние, как будто «сработала» стандартная команда EMT 36. Кажется, эта фраза нам уже встречалась? Ну да, это же эмуляция, но на этот раз не EMT 20, а EMT 36. Конечно, эта задача гораздо сложнее, но и цель ведь куда серьёзнее. Если удастся реализовать такой системный драйвер, то он будет работать со всеми, без исключения, программами пользователя на любых языках и в любых режимах, осуществляя обмен с магнитофоном в спецформате, если только программа пользователя выходит на обмен по стандартному прерыванию EMT 36. Автору это удалось, и теперь он работает с магнитофоном на скорости 4000 бод вместо стандартных 1200 (поясним, что бод - это единица скорости обмена информацией, соответствующая одному бит/с).
Точно так же можно организовать обмен с дисководом (без всякой операционной системы), с квазидиском на базе дополнительного ОЗУ (которое на БК может в принципе иметь объём порядка 2-4 Мб) и даже с принтером - при этом на принтер будут выдаваться для печати те самые блоки информации, которые мы хотели бы записать на МЛ. Правда, для принтера мы уже знаем более удобный способ. Важно то, что для такого обмена не потребуется опять-таки никакого вмешательства в программу пользователя, лишь бы она была рассчитана на работу с магнитофоном[6].
А другие EMT? К примеру, вам не нравится алгоритм векторной графики БК-0010 (он, и правда, не самый лучший, прежде всего слишком медленный). Отлично, напишем свою программу, но не станем просить завод, чтобы он выпустил новое ПЗУ, а просто перехватим EMT 32 и заменим векторную графику БК на свою.
Хотите заменить знакогенератор? Нет проблем. Всё то же EMT 16 (и эмулированное EMT 20) позволят решить задачу. Надо только выделять часть символов, которые мы хотим заменять (это могут быть, например, латинские символы или псевдографика), перехватывать их коды и, вместо того чтобы вывод этих символов «отдать на откуп» EMT 16, рисовать их на экране самостоятельно, пользуясь своими масками начертания. Конечно, для этого надо хорошо знать, как это делает драйвер дисплея БК-0010, но это уже дело вашей настойчивости и упорства. Подскажем лишь, что тут «замешаны» две подпрограммы по адресам @#102764 и @#103222. С их помощью, указав в регистре R1 адрес начала строки маски, можно вывести на экран символ независимо от того, находится его маска в ОЗУ или в ПЗУ, стандартная она или «самодельная».
А прерывание TRAP? Например, в языках высокого уровня по «трапам» выполняются многие процедуры. Почему бы их не «поймать», как мы это делали с EMT, и не извлечь из этого пользу? Например, ввести свои дополнительные процедуры или изменить алгоритмы имеющихся. Конечно, для этого надо знать структуру интерпретатора языка (например, Фокала) так, как будто это вы его придумали...
А прерывания от клавиатуры? Векторы @#60 и @#274 не изменяются почти ни в каких программах пользователя. Этим можно воспользоваться и ввести по ним дополнительные директивы почти в любую программу. Удобнее всего использовать коды в регистре «АР2» для клавиш «РУС», «ЛАТ» и «ВВОД» - эти коды не обрабатываются практически никакими программами и их можно применять для своих целей без помех. Например, нажатием клавиши «АР2»/«ЛАТ» можно вызвать ту же подпрограмму графического копирования экрана. Покажем, как это можно сделать, на примере:
; Инициализация драйвера MOV PC,R0 ; Вычислить адрес ADD (PC)+,R0 ; супервизора .@SQN+2 ; в перемещаемом формате MOV R0,@#274 ; Записать новый вектор @#274 HALT ; Стоп ; Супервизорная программа - обработка прерывания по "АР2" SON: CMPB #17,@#177662 ; Клавиша "ЛАТ" по регистру "АР2"? – BNE 0 ; Нет - выход CALL GRK ; Графическая копия экрана 0: JMP @#101362 ; Передача управления в драйвер ; клавиатуры БК-0010
После всех предшествующих примеров можно было бы лишний раз и не повторять, что эту программу можно также использовать в составе общего универсального драйвера принтера, что её тоже надо защитить от EMT 14, и т.д.
Ясно, что тем же путём можно ввести дополнительные директивы в любую программу пользователя, например, не выходя из редактора МИКРО.10К, вызвать программу печати «от метки до курсора», надо лишь знать, что в МИКРО адрес метки хранится в ячейке @#426, а адрес курсора - в ячейке @#432. Удобно, не правда ли? И программа получится очень простой, всего в несколько десятков команд.
Итак, теперь вы знаете, что такое системные драйверы. А почему автор назвал это «концепцией»? Потому что, как было сказано в начале раздела, есть два пути - либо приспосабливать каждую программу пользователя для данной цели, например для печати на конкретном принтере, либо сделать это сразу для всех программ, написав резидентный драйвер. Это именно концепция, или, как сказано в словаре иностранных слов, «система взглядов на те или иные явления». До сего дня мало кто из программирующих на БК решает указанные проблемы с системных позиций, вследствие чего и множится необоснованно число версий редакторов и языков, например варианты EDASP для печати на «Роботроне», на МС-6312, на МС-6313, на телетайпе... И всё это - самостоятельные программы, а что в них «самостоятельного»? Только драйвер принтера. И ради его изменения нужно «перепахать» такую сложную программу, как EDASP. Стоит ли?
Контрольные вопросы и задания
- Предложите способ, который позволит всю информацию, выводимую любой
программой на экран, записать в виде файла на МЛ. Какое значение может
иметь такой способ, например, для работы с Фокалом? С дизассемблером?
-
Нужно написать системный драйвер, аналогичный драйверу принтера для печати текста. Но вместо вывода символов на принтер надо выделить свободную область памяти, куда записывать последовательно коды появляющихся на экране символов. Количество байтов получившегося массива надо подсчитывать. Окончание формирования массива может быть по достижении им заданной длины или по специальной директиве, после чего с помощью EMT 36, зная адрес и длину массива, надо записать его на МЛ в виде файла. При работе с Фокалом такой приём даёт возможность, например, записать листинг Фокал-программы на МЛ, а затем загрузить его в редактор текста. Это пригодится, если нужно обработать описание или учебную программу, написанные на Фокале, включить Фокал-программу в текст, написанный с учебными целями, и т.п. При работе с дизассемблером это позволит дизассемблированный текст программы в кодах прокомментировать в редакторе, а отредактировав его и приведя к формату, например, МИКРО.10К, даже оттранслировать в ассемблер-системе или включить в качестве блока в свою программу[7].
-
- Напишите блок для «повторного», без разрушения
памяти, выхода из МСД в Фокал, пользуясь указаниями о порядке такого
выхода, приведёнными в конце раздела о директивах МСД. Предусмотрите
защиту блока выхода в Фокале.
-
Для выхода из МСД в Фокал надо проделать следующее.
Щ120020А26Д0П262А177777И «СТОП»
Реализуем это в виде программного блока:
BEG: CLR R0 ; Адрес пересылки информации MOV #120020,R1 ; Адрес информации в ПЗУ-Фокал 1: MOV (R1)+,(R0)+ ; Пересылка блока CMP #120046,R1 ; информации длиной 26 BGE 1 ; в системную область MOV #-1,@#262 ; Запись признака кода "ВВОД" MOV PC,R0 ; Вычисление адреса ADD (PC)+,R0 ; начала блока в, .@BEG+2 ; перемещаемом формате MOV R0,@#1744 ; Ограничить доступную Фокалу ; область памяти началом ; блока выхода в Фокал HALT ; "СТОП" - выход в Фокал
Конечно, реализовать эту программу можно и по-другому, это только один из возможных вариантов.
-
Системная область БК-0010
Мы уже неоднократно упоминали этот термин - системная область. Пора, наконец, рассказать подробно, что это такое. Для БК-0010 системной областью называется область адресов от 0 до 777. В ней помимо уже известных нам векторов прерывания размещаются системные переменные и стек. Стек - это область памяти, обычно адресуемая косвенно через регистр SP, и подробно мы её рассмотрим позже (в приводимых ранее примерах мы её уже использовали). А системные переменные - это специально выделенные ячейки памяти, используемые в качестве буфера для работы драйверов монитора БК-0010. В них запоминаются различные промежуточные данные, признаки режимов ЭВМ и т.п. Часть ячеек свободна и может быть задействована программистом для своих нужд. Однако при выборе таких ячеек надо иметь в виду, что некоторые из них могут использоваться в языках высокого уровня или некоторыми распространёнными программами, в частности ассемблер-системой.
Приведём сведения о всех ячейках системной области в порядке возрастания их номеров, включая как уже известные нам, так и те, с которыми мы пока не встречались. Для всех ячеек будут даны сведения о хранимой там информации, по возможности с пояснениями, что это такое и как эти ячейки используются системой БК-0010 или ЦП. Если информацию, хранящуюся в ячейках, можно как-либо применять в своих программах, постараемся обратить внимание и на это. Для каждой ячейки будет приводиться её адрес. Если используется машинное слово или несколько слов, будет приведён только адрес (адреса) ячеек.
Если же задействован отдельный байт, это будет оговариваться особо.
У читателя может возникнуть вопрос, откуда автор получил сведения о системных ячейках. Во-первых, был использован авторский текст драйверного модуля БК-0010 (листинг DMBK, автор - М.И. Дябин, Москва, 1983-1985 гг.). Во-вторых, производилось дизассемблирование содержимого ПЗУ БК-0010. И наконец, со многими ячейками автор проводил эксперименты для определения их функционального назначения и возможного использования.
★ ★ ★
- 0, 2 - не используется. В эти и некоторые другие неиспользуемые ячейки при включении ЭВМ заносится адрес перезапуска системы - 100000. При случайном косвенном обращении по адресу такой ячейки произойдёт инициализация системы.
- 4, 6 - вектор прерывания по клавише «СТОП» и команде останова HALT. Прерывание по клавише «СТОП» внеприоритетное. Значение первого слова вектора в ПМ - 100442, в МСД - 160722, в Фокале - 121110. в Бейсике - 120234 (если это значение не изменено программой пользователя).
- 10, 12 - вектор прерывания по резервному коду. ЦП выполняет данное прерывание при получении кода, отсутствующего в его наборе команд.
- 14, 16 - вектор прерывания по T-разряду (при работе в пошаговом режиме).
- 20, 22 - вектор прерывания по команде IOT.
- 24, 26 - вектор прерывания по аварии сетевого питания ЭВМ (по входу ACLO ЦП). Это прерывание внеприоритетное.
- 30, 32 - вектор прерывания по команде EMT.
- 34, 36 - вектор прерывания по команде TRAP.
- 40-56 - ячейки (байты) слова состояния дисплея. Они уже были описаны в разделе, посвящённом EMT 34, там же приведена таблица их значений. Напомним, что исходное содержимое всех этих ячеек (при перезапуске ЭВМ) равно нулю, а при включении соответствующего режима дисплея становится равным 377, за исключением ячейки 43 - индикатора регистра «РУС», куда заносится число 200 (маска регистра «РУС»). Ячейки могут быть использованы прежде всего для определения включённых в данный момент режимов с целью их изменения и для приведения изображения на экране к желаемому виду и формату. Запись числа 377 в некоторые из ячеек (44-47, 54-56) приводит к прямому включению данного режима, иногда - с некоторыми нюансами по сравнению с включением режима подачей кода через EMT 16. Например, при включении таким образом режима «БЛОКРЕД» и «ИНДСУ» отсутствует индикация в служебной строке; при записи числа 377 в ячейку 56 курсор со следующего выводимого символа исчезает, но остаётся его «призрак» на экране и т.п. Тем не менее эти ячейки могут в некоторых случаях очень успешно использоваться для переключения режимов, особенно когда это переключение должно выполняться с очень высокой скоростью, на несколько порядков быстрее, чем по EMT 16. Записав в такие ячейки число, отличное от 0 и 377, можно сделать режим «неотменяемым» с клавиатуры - никакие нажатия клавиш БК не позволят снять его.
Другие ячейки (40-43, 50-53) при записи в них какого-либо числа включают режим частично и «ненормально» - дальнейшая работа с дисплеем становится невозможной или затруднительной. Но и эти ячейки могут быть использованы для каких-либо целей. Например, регистр «РУС»-«ЛАТ» можно переключать через ячейку 43. но при этом поменяется на обратную и установка регистра «СТР»-«ЗАГЛ». Ячейка 41 проявляет себя только в отдельных случаях, например после переключения цветов. Ячейка 40 блокирует выведение на экран каждого второго символа (выводятся широкие символы), а ячейка 42 по достижении края экрана блокирует рулонный сдвиг и как бы организует режим «РП» на обычном экране. Ячейки графических режимов 50-52 хотя и переключают дисплей в режим текстовой графики, но при этом на экране остаётся тень символьного и графического курсоров, резко замедляется отработка команд текстовой графики и т.п.
- 57 (байт) - не используется.
- 60, 62 - вектор прерывания от клавиатуры[8] (кроме регистра «АР2», или «НР» на старой модели БК-0010 с плёночной клавиатурой). Приоритет данного прерывания - 4.
- 64-76 - не используются.
- 100, 102 - вектор прерывания от внешнего таймера. Вход данного прерывания выведен на контакт В1 разъёма порта «УП» БК-0010. Приоритет прерывания - 4.
- 104 (байт) - код последнего введённого с клавиатуры символа. Код заносится в ячейку, если разрешено прерывание от клавиатуры (в отличие от регистра 177662, где код появляется после нажатия клавиши независимо от того, разрешено ли прерывание). Ячейка широко используется в различных программах для организации режима «автоповтор» - многократного выведения символа на экран при удержании клавиши нажатой. Код символа в данной ячейке, в отличие от регистра 177662, - уже «обработанный», т.е. соответствующий регистру клавиатуры («РУС», «ЛАТ», «СТР», «ЗАГЛ»),
- 105 (байт) - признак записи кода в ячейку 104[9] Используется командой EMT 6.
- 106 - буфер константы повтора (количество повторов «пустого» цикла SOB при нажатии клавиши «ПОВТ» перед выводом каждого следующего символа). В символьном режиме число в ячейке 106 равно 20000, в графическом - 2000, благодаря чему работа клавиши «ПОВТ» замедляется до приемлемого уровня. Перезадав содержимое ячейки 106, можно изменить скорость работы в режиме «повтор» (минимальной скорости соответствует число 0[10]. максимальной - 1).
- 110 (байт) - признак повтора кода. Если значение в ячейке 110 не равно нулю, то код последнего введённого символа выводится повторно, после чего ячейка сбрасывается.
- 111 (байт) - счётчик табуляции. В соответствии с содержимым ячейки устанавливается количество пробелов, выводимых на экран. Используется для отработки табуляции по клавише «ТАБ».
- 112-120 - позиционный код маски табуляции. В соответствии с содержимым этих четырёх слов (64д бита) устанавливаются метки табуляции в служебной строке и выполняется табуляция по клавише «ТАБ». Установленной метке табуляции соответствует единичный бит, сброшенной - нулевой. Это можно использовать для «принудительного» (программного) задания позиций табуляции, для чего достаточно занести в данные ячейки соответствующую маску. Для того чтобы метки табуляции индицировались в служебной строке, её после записи маски в ячейки 112-120 необходимо «обновить», дав команду «установка индекса» (код 236).
- 122 (байт) - счётчик ключа. Количество символов, выводимое на экран при обращении к программируемому «ключу» клавиатуры (клавиши «0»...«9» по регистру «АР2»). В эту ячейку заносится длина текста текущего ключа.
- 123 (байт) - не используется.
- 126-150 адрес очередного байта текста текущего «ключа».
- 126-150 - адреса (по порядку) начала текстов программируемых «ключей», соответствующих клавишам «0», «1»,...«9».
- 152 (байт) - признак нарушения рулона. Используется для организации рулонного сдвига экрана вверх и вниз.
- 153 (байт) - признак записи точки. Используется в программах командных прерываний EMT 30 и EMT 32.
- 154 (байт) - маска позиции графической точки (ГТ). Чтобы адресовать ГТ на экране в режиме текстовой графики, ёмкость машинного слова недостаточна, требуется ещё как минимум три бита. Данная ячейка и является дополнением к машинному слову - адресу ГТ. Маска ГТ устанавливает положение графической точки в пределах выбранного байта экранного ОЗУ и может принимать значения 1, 2, 4, 10, 20, 40, 100, 200 (для черно-белого режима 512 точек) - единичный бит маски соответствует положению ГТ в пределах байта. В цветном режиме маска состоит не из одного, а из пары битов: 3, 14, 60, 300. Положение пары единичных битов кратно двум, т.е. в качестве маски используются попарно биты 00 и 01, 02 и 03 и т.д.
- 155 (байт) - начальная маска ГТ. Используется для задания начального значения маски в ячейке 154, которое может быть равно 1 или 3, в зависимости от режима (чёрно-белого или цветного).
- 156 - номер символа на экране. Номер первого символа (в верхнем левом углу) равен 0, последнего (в правом нижнем) - 2777 (для режима «64 символа в строке»). Нумерация позиций символов от сдвига рулона не зависит и всегда одинакова. Последний символ любой строки имеет номер, оканчивающийся на 77, первый - на 00. При переходе номера через нуль в двух младших разрядах драйвер дисплея организует перевод строки на экране. Проиллюстрировать эту функцию ячейки можно следующими программами:
0: CLR @#156 EMT 6 EMT 16 BR 0 0: MOV #77,@#156 EMT 6 EMT 16 BR 0
Функция перевода строк на экране нарушается: при работе первой программы курсор возвращается к левому краю экрана, но всего на одну телевизионную строку экрана ниже, а при работе второй символ вообще печатается только на одном месте в начале строки.
В цветном режиме содержимое ячейки 156 кратно двум. Его можно использовать в различных программах, например, для индикации достижения текстом края экрана и организации автоматического перевода строки на принтере[11].
- 160 - адрес текущего символа. В этой ячейке всегда присутствует абсолютный (с учётом рулонного сдвига) адрес верхнего левого байта формируемого символьного курсора. Если записать в эту ячейку другое значение, то последующие символы, выводимые по EMT 16 или по EMT 20, будут формировать строку начиная с этого адреса. Строка прервётся, когда содержимое ячейки 156 в двух младших разрядах перейдёт через нуль или в строке символов встретится невыполнимая в пределах одной строки команда, например «ВК» или «очистка экрана». При этом курсор вернётся на прежнее место экрана (содержимое ячейки восстановится в соответствии с номером символа). Символ по заданному в ячейке 160 адресу может быть выведен и в ОЗУ пользователя, скажем, по адресу 30000. Хотя при этом он не виден, но может быть использован, например, для получения на экране его увеличенной «копии» (в различных заставках), «бегущей строки» и т.п.
- 162 - горизонтальный размер символа на экране, в байтах. В этой ячейке хранится величина горизонтального смещения курсора после формирования очередного символа. В режиме «64 символа в строке» она равна 1, в режиме «32» - 2. Меняя содержимое ячейки, можно получить оригинальные эффекты вывода символов «вразрядку», по вертикали, диагонали. назад:
0: MOV #1001,@#162 ; Интервал вывода MOV #TEX,R1 ; Начало текста CLR R2 ; Признак конца ; текста EMT 20 ; Выдать текст EMT 6 ; Ждать нажатия клавиши BR 0 ; Программа зациклена ТЕХ: .A:Привет! .E END
Однако при использовании данного приёма необходимо учитывать, что по достижении «физического» конца экрана (который при сдвинутом рулоне может прийтись на любое место) и попытке «вывода» символов в область ПЗУ компьютер зависнет, так как соответствие номера символа и рулона нарушается.
- 164 - количество знакомест экрана (3000 в обычном режиме и 400 в режиме «РП»).
- 166 - адрес (номер) байта текущей ГТ на экране. Не зависит от сдвига рулона, левый верхний угол экрана всегда 0, правый нижний - 35777. Может использоваться вместе с ячейкой 154 для вычисления (и, например, индикации) позиции курсора в режиме текстовой графики.
- 170 - абсолютный адрес байта текущей ГТ на экране. При начальном положении рулона меняется от 42000 до 77777. Меняется при сдвиге рулона.
- 172 - длина графического вектора. Используется при исполнении команд текстовой графики.
- 174 - счётчик телевизионных строк. Используется при перемещении графического курсора в пределах одного знакоместа, для организации рулонного сдвига экрана в режиме текстовой графики.
- 176 - буфер координаты X для EMT 30 и EMT 32.
- 200 - буфер координаты Y для EMT 30 и EMT 32.
Две последние ячейки могут широко использоваться в различных функциях графических редакторов и других программ с применением EMT 30 и EMT 32 для получения координат последней выведенной точки (конца вектора).
- 202 - адрес начала видеопамяти. Вне зависимости от рулонного сдвига экрана в этой ячейке всегда число 40000 (в режиме «РП» - 70000). Может использоваться как константа адреса конца ОЗУ пользователя, для определения режима (обычный или «РП») и т.п.
- 204 - относительный адрес начала экрана (или адрес конца служебной строки). В исходном положении рулона - 2000, меняется со сдвигом рулона. Иначе это число называется базой видеопамяти.
- 206 - длина видеопамяти в байтах, 40000 - в обычном режиме и 10000 - в «РП».
- 210 - длина рабочей области экрана в байтах (равна длине видеопамяти за вычетом длины памяти служебной строки), 36000 - в обычном режиме и 5000 - в режиме «РП» (так как в «РП» ещё 1000 байт отводится на нижнюю черту - границу экрана). Все эти константы используются драйверами дисплея для адресации графических точек и символов на экране. В своих программах их можно использовать для автоматического учёта границ экрана при переходе в режим «РП» и коррекции выводимых на экран изображений.
- 212 - маска цвета фона экрана. В БК-0010 реализованы четыре цвета - красный, зелёный, синий и чёрный, которым соответствуют маски 177777, 125252, 52525 и 0. Например, если фон экрана чёрный, то маска в ячейке 212 равна 0. Если экран инверсный в чёрно-белом режиме (или при красном цвете фона в цветном режиме), маска равна 177777 и т.д. Изменяя содержимое ячейки 212, можно менять цвет фона[12], делать экран «полосатым» и т.п.
- 214 - маска цвета символа на экране. Маски символов точно такие же, как для фона. Помимо символов, текущая маска в ячейке 214 «отвечает» за цвета графических точек и векторов. Комбинируя различные маски (не только четыре вышеуказанные) по вертикали и горизонтали, можно значительно расширить цветовую па литру БК-0010, а задавая специально подобранные значения (например, 100001 в цветном режиме), можно «маскировать» выводимые символы. Вообще же, комбинируя маски фона и символа, можно получить самые разнообразные эффекты.
- 216 - маска цвета фона служебной строки.
- 220 - маска цвета символа служебной строки. Эти две ячейки играют для служебной строки ту же роль, что две предыдущие для всего экрана.
- 222 - счётчик кодов.
- 224 - счётчик установки индикаторов.
Две последние ячейки используются при выдаче в служебную строку индикаторов - надписей, указывающих на режимы дисплея: «РУС», «ЛАТ», «ГРАФ», «ЗАП», «СТИР», «БЛР», «ИСУ», «РЕД».
- 226-246 - не используются
- 250-254 - буферы служебных констант драйвера телеграфного канала.
- 256 - ячейка, зарезервированная под копию порта вывода (драйверами монитора БК-0010 не используется).
- 260 - адрес дополнительной программы обработки прерывания от клавиатуры. Если занести в эту ячейку какой-либо адрес, то управление будет передаваться на него при нажатии на любую клавишу. При нормальном функционировании ЭВМ код в эту ячейку заносится автоматически драйвером клавиатуры (ячейка обнуляется, что означает отказ от вызова дополнительной программы и продолжение обработки драйвером нажатия клавиши).
- 262 - признак кода «ВК». Если в этой ячейке содержится нуль, то при нажатии клавиши «ВВОД» в программу передаётся код 12 (большинство режимов), иначе - код 15 (например, в Фокале).
- 264 - буфер стартового адреса. В эту ячейку заносится физический адрес последнего загруженного файла (независимо от адреса, указанного в оглавлении файла) и на него передаётся управление по директиве пускового монитора «S» (без указания адреса в явном виде).
- 266 - буфер длины массива. В эту ячейку заносится длина последнего считанного файла.
- 270, 272 - не используются (это адрес вектора прерывания IRQ3 ЦП, не задействованного на БК аппаратно).
- 274, 276 - вектор прерывания от клавиатуры по нижнему регистру[13] («АР2» на БК-0010.01).
- 300 (байт) - «флаг» фазы читаемого с МЛ сигнала, устанавливается при чтении равным 0 или 377 (прямой или инверсный сигнал).
- 301 (байт) - признак ошибки при работе драйвера магнитофона. Данная ячейка дублирует ячейку 321 блока параметров EMT 36. причём она является первичной, а ячейка 321 - вторичной.
- 302 - признак фиктивного чтения файла драйвером магнитофона. При фиктивном чтении эта ячейка не равна 0.
- 304 - инкремент адреса массива при работе драйвера магнитофона. В БК-0010 содержимое этой ячейки при чтении файлов всегда равно 1 (при фиктивном чтении - 0). В случае его изменения в некоторых неграмотно написанных копировщиках возникает характерный дефект при загрузке файла - косая «сетка» на экране (отдельные байты, далеко отстоящие друг от друга) и порча отдельных байтов всех массивов, находящихся в памяти дальше начала загружаемого файла[14].
- 306 - буфер адреса блока параметров EMT 36, дублирует содержимое регистра R1 и используется при работе драйвера магнитофона.
- 310 - буфер указателя стека, сюда заносится и отсюда восстанавливается (по клавише «СТОП» или по окончании работы EMT 36) адрес вершины стека при работе драйвера магнитофона.
- 312 - буфер контрольной суммы массива (файла). В эту ячейку заносится контрольная сумма при чтении или записи файла на МЛ. Кроме того, она используется как буфер загрузки при фиктивном чтении файлов.
- 314 - константа сравнения при чтении файла. Сюда заносится результат вычислений константы по установочной последовательности. Это число позволяет ЭВМ отличать при чтении файла нулевой бит от единичного. Используется драйвером магнитофона при чтении, может также использоваться для примерной оценки скорости при записи.
- 316 - не используется. Это единственная из неиспользуемых ячеек, содержимое которой не меняется при исполнении EMT 14 (не считая ячеек стека)[15]. Особая просьба: не использовать эту ячейку в пользовательских программах, а оставить для системных драйверов, которые пишет автор этой статьи (драйвер спецформата магнитофона, драйвер дисковода и т.п.), иначе ваши программы просто будут несовместимы с этими системными программами и вам же будет хуже.
- 320-371 - блок параметров драйвера магнитофона. Назначение этих ячеек было подробно изложено при описании EMT 36.
- 372-777 - стек.
★ ★ ★
Наше повествование о системной области подходит к завершению. Осталось сказать несколько слов о стеке.
Как вы уже знаете, стек - это специально выделенная область памяти, куда сама ЭВМ или программы пользователя заносят данные, которые нужно сохранить (обычно временно). Адресуется стек чаще всего через специальный регистр R6, или SP, отличающийся от остальных регистров процессора тем, что его автоинкремент или автодекремент выполняется всегда на 2, независимо от того, работает команда со словом или с байтом.
Отсчёт ячеек стека, в отличие от всех прочих участков памяти, ведётся «сверху вниз», т.е. началом считается адрес 777 (или, в чётных адресах, 776), а концом (или «дном») - адрес 372. Если программа, использующая стек, не работает с магнитофоном или организует блок параметров для него начиная не с адреса 320, а в другом месте ОЗУ, то стек может быть «продлён» до адреса 320.
Ячейка, адрес которой соответствует текущему содержимому SP, называется вершиной стека. Адрес вершины при работе со стеком не остаётся постоянным, а всё время меняется. Но этот адрес всегда должен восстанавливаться при работе отдельных ветвей программы. Таким образом, если мы в начале программы заносим что-либо в стек, изменяя адрес его вершины, а затем программа ветвится. то мы должны восстановить адрес вершины до ветвления или предусмотреть одинаковый порядок его восстановления в каждой из ветвей (либо после выхода из них). Нарушение этого правила может привести к тому, что стек будет постоянно засоряться всё новыми данными и рано или поздно затрёт ячейки системной области. К чему это приведёт, ясно без лишних комментариев[16].
Стек в БК-0010 организуется по типу магазина для автоматического оружия, а обмен данных в нем - по принципу «первым вошёл - последним вышел». Данные, введённые раньше, оказываются в стеке дальше от вершины (и «выше» в памяти), чем введённые позже, и данные, введённые последними, извлекаются из стека прежде всего. Но это всего лишь традиция и удобный приём обращения к стеку. Не составляет большого труда организовать хранение данных и по другому принципу, например «первым вошёл - первым вышел» («очередь»), но это, как правило, менее удобно.
Обычно данные сохраняются в стеке с помощью стандартного приёма - последовательной засылкой операторами MOV с автодекрементной адресацией регистра R6 (последовательное извлечение с помощью автоинкрементной адресации). По ходу изучения ассемблера приводилось столько примеров с использованием стека, что повторять их нет необходимости. Мы познакомились также ещё с двумя возможностями сохранения и использования данных в стеке: с помощью косвенной индексации (чтобы «достать» данные, лежащие не на вершине) и малоупотребительной команды MARK. Но есть ещё один способ использования стека для хранения данных - с помощью абсолютной адресации.
При реализации этого способа исходят из того, что для работы подавляющего большинства пользовательских программ такая «глубина» стека, которая имеется на БК-0010, не нужна. Вполне достаточно иметь «дно» стека по адресу 600, а иногда даже 700. При этом всю расположенную ниже область памяти можно рассматривать как системные ячейки, не используемые драйверами монитора БК-0010. Естественно, что любая из этих ячеек может быть использована программистом для своих нужд. Например, этот приём широко применяется в МИКРО.10К: начиная с адреса 400 по 600 многие ячейки используются для хранения констант и переменных ассемблер-системы. Перечислим некоторые из них, они могут быть полезными при работе с МИКРО.10К:
- 402 - адрес начала текста редактора,
- 404 - адрес текущего конца текста редактора,
- 426 - текущий адрес метки,
- 432 - текущий адрес курсора,
- 446 - адрес начала текущей страницы текста,
- 470 и далее - буфер маски поиска.
Разумеется, используются в МИКРО и другие ячейки. Благодаря такой организации системных переменных МИКРО.10К как программа, не имеющая внутри себя буфера переменных может с равным успехом работать и в ОЗУ пользователя, и в дополнительном ОЗУ, и в ПЗУ, что является примером рациональной организации инструментальных программ. Попутно укажем, как можно изменить МИКРО.10К таким образом, чтобы изменились параметры буферов текста и загрузочного модуля, но сначала сделаем одно замечание.
Многие решительно выступают против того, чтобы использовать выделенные ячейки стека (и вообще свободные ячейки системной области) для хранения переменных в программах пользователя, и не без оснований: если в памяти должны работать две программы и обе используют по роковому стечению обстоятельств одни и те же ячейки, то они будут мешать друг другу, и, скорее всего, ни одна из них нормально работать не сможет. Что можно на это возразить? Во-первых, вариант, когда в памяти БК-0010 работает сразу несколько программ, встречается не так уж часто. А во-вторых, где прикажете размещать переменные, если программа должна быть рассчитана на работу в ПЗУ? А этот вариант становится всё более распространённым[17]. Словом, как уже было сказано раньше, вам самим решать, что и каким образом использовать. Автор же, придерживаясь демократических взглядов, от категорических рекомендаций и запретов предпочитает воздерживаться.
Но вернёмся к обещанным рекомендациям по изменению МИКРО. В исходной версии МИКРО.10К-РП (или, равным образом. МИКРО.10-01К) адрес начала текста равен 17001, адрес загрузочного модуля (программы в кодах, получаемой в результате трансляции или трансляции и компоновки) - 13000. Таким образом, для текста программы выделено 20000 байт (последние 1000 байт ОЗУ ассемблер использует под таблицу меток), а под загрузочный модуль - 4000. Это достаточно рациональное распределение памяти, если ассемблер-система расположена в ОЗУ по адресу 1000. А если ассемблер размещается в ПЗУ (или дополнительном ОЗУ), например, по адресу 140000? Ясно, что первые 12000 байт ОЗУ пользователя при этом будут потеряны. Как перераспределить память?
В МИКРО.10К для этого есть три ячейки. Если обозначить адрес загрузки ассемблер- системы за 0, то в ячейке 6 содержится адрес начала текста редактора, в ячейке 32 - адрес начала загрузочного модуля, а в ячейке 36 - тот же адрес минус 1000 (используемый при вычислении адресов меток). При размещении МИКРО.10К в области ПЗУ целесообразно задать адрес текста равным 10001, адрес загрузочного модуля - 1000, а третью константу - 0. Разумеется, распределение памяти может быть и иным, в зависимости от целей применения МИКРО.10К. Например, если МИКРО используется только как текстовый редактор, можно задать адрес начали текста 1001, а две другие константы не изменять. При размещении МИКРО.10К в ОЗУ пользователя и применении его в качестве текстового редактора также можно увеличить буфер текста, задав адрес его начала (в ячейке 6), например, равным 13001. Кстати, единица в адресе начала текста МИКРО.10К совсем не обязательна, он может быть любым, просто такой адрес - опознавательный признак текстового файла МИКРО.10К и, кроме того, для нормальной работы текстового редактора желательно иметь перед буфером текста свободный байт, куда при запуске записывается код 12. После изменения параметров буферов МИКРО надо помнить, что для их реализации необходимо перезапустить МИКРО.10К с адреса загрузки.
★ ★ ★
Бывают случаи, когда стек, расположенный по адресам 372...777, начинает мешать работе. Допустим, нам надо скопировать файл длиной 77400 с адресом загрузки, равным 400 (такие игровые программы есть, в них подобный адрес и длина файла - своеобразный, но очень «слабый» способ защиты от копирования). Сделать это обычным копировщиком, размещаемым в ОЗУ, нельзя - всё ОЗУ занимает копируемый файл. Но и обычный копировщик, размещённый в дополнительном ОЗУ или ПЗУ, тоже непригоден - мешает стек. Как быть? Нужен копировщик, размещаемый в дополнительном ОЗУ (например, с адреса 140000) и переносящий стек в эту же зону. А для переноса стека достаточно изменить содержимое регистра SP.
Ещё один случай, когда надо представлять, что хранится в стеке, - это создание программ с автозапуском. Такие программы обычно имеют адрес загрузки меньше 1000 и после загрузки самозапускаются. Как это происходит? Довольно просто. При обращении к драйверу магнитофона по прерыванию EMT 36 в стек заносится адрес команды, следующей за вызвавшей прерывание. Если теперь мы загрузим программу, в начале которой (до адреса 1000) записан блок, состоящий из слов, содержащих адрес запуска, то адрес команды, следующей после вызова EMT 36, будет в стеке подменен. После возврата из прерывания (после загрузки файла) этот адрес по команде RTI попадает в PC и управление передаётся на запуск загруженного файла. В МСД исходный адрес вершины стека - 732, сюда обычно и заносят адрес запуска программы с автозапуском. В ПМ исходный адрес вершины стека - 1000 и адрес автозапуска обычно заносят по адресу 776 или 766 - в обоих случаях автозапуск «сработает». Автозапуск по ячейке 766 можно задать прямо в МСД, записав затем на МЛ файл с адреса, например, 760. Для задания автозапуска по другим ячейкам необходимы особые программные средства, например специальный копировщик[18]. Обычно такие средства заносят адрес запуска программы начиная с ячейки 732 (или даже с ещё более «низких» адресов) до 1000, что обеспечивает автозапуск из любого режима.
Однако у автозапуска есть недостатки. Во-первых, после загрузки программа запускается без проверки на наличие ошибок чтения, а во-вторых, EMT 36, как правило, заканчивает при этом работу «нештатным» образом и дистанционное управление магнитофона не отключается, если только в запущенной программе пользователя не присутствует команда, делающая это взамен неоконченного EMT 36 (например, команда EMT 14). Разработаны (в том числе автором) несколько способов автозапуска, позволяющих устранить указанные недостатки, но на этом мы останавливаться не будем. Отметим только, что основаны они на том, что в начале программы помещается блок (при загрузке размещаемый ещё «глубже» в стеке), на который автозапуск и передаёт управление, а уже этот блок делает всё необходимое, в том числе и перемещение загруженной программы по рабочему адресу (что очень важно, если автозапуском снабжена программа, начинающаяся не с адреса 1000), а уже потом запускает её.
В заключение следует сказать, что автозапуск довольно коварный приём и снабжать им все программы подряд не рекомендуется. Особенно это относится к системным и инструментальным программам, которые автозапуск может даже иногда сделать неработоспособными (например, если для этого требуется обязательный «штатный» выход из режима загрузки: запись в нужные ячейки всех переменных, байта ответа EMT 36. восстановление стека и т.п.).
Для чего предназначен ассемблер?
«Вот это да! - скажет иной читатель. - Сначала мы изучили язык, а потом задаём вопрос, зачем он нужен?!» Но вопрос этот не праздный. Хотя язык ассемблера относится к машинно-ориентированным языкам, а значит, по сути своей универсален, есть определённые классы задач, для решения которых он подходит больше, а для других - меньше. Что же это за задачи?
Поскольку программы, написанные на ассемблере, имеют максимальное быстродействие, ясно, что преимущества языка более выражены при операциях с большими массивами информации, а также при работе в реальном времени. Это может быть:
- пересылка массивов, в том числе текстов и изображений, их обработка и модификация;
- сортировка, перекодирование, перевод из одного формата в другой и обратно, упаковка и распаковка текстов и изображений. поиск в массивах заданной информации;
- логическая обработка информации (логические игры и головоломки, логическое моделирование процессов);
- скоростной анализ ситуаций и генерация динамических моделей (в играх и при управлении внешними объектами);
- связь с внешними устройствами в заданных форматах и с заданными скоростями обмена.
Теперь посмотрим, для решения каких задач ассемблер приспособлен меньше всего. Как известно, система команд процессора БК-0010 содержит только простейшие арифметические операции с целыми числами - сложение и вычитание. Не так уж трудно на их основе построить программы целочисленного умножения и деления, но даже построенные по оптимальным алгоритмам, эти операции будут выполняться на порядок медленнее, чем элементарные, а программы - занимать довольно большой объём памяти. Стоит ли говорить тогда о программах плавающей арифметики, вычисления функций и т.п.? Вряд ли удастся при их написании заметно улучшить алгоритмы аналогичных вычислений, имеющиеся в языках высокого уровня, а занимаемый объём ОЗУ при этом станет так значителен, что оставшаяся для других нужд память заметно сократится. В то же время выигрыш по быстродействию по сравнению с языками высокого уровня будет не так уж велик. Таким образом, ассемблер меньше всего подходит для решения задач вычислительного характера.
Но иногда включение вычислений в прочие программы на ассемблере бывает совершенно необходимо. Например, нередко в ходе управления объектами (или в играх) приходится вычислять траектории движения по формулам, генерировать псевдослучайные последовательности и т.п. Конечно, в наборе прикладных программ, разработанных для БК-0010, есть математические пакеты, реализующие любые функции плавающей арифметики (например, пакет ARPAC), и даже специальные языки - расширения ассемблер-системы (язык ALMIC, 1991 г.). Но, как уже отмечалось, программы плавающей арифметики работают относительно медленно (хотя, конечно, гораздо быстрее, чем, например, Фокал) и занимают много памяти. Чтобы не терять присущие ассемблеру преимущества - быстродействие и компактность программ, можно предложить несколько особых приёмов.
Все вычисления нужно пытаться сводить к целочисленной арифметике в пределах одного машинного слова (однорегистровая арифметика). В самом деле, одно машинное слово может содержать число от 0 до 65535д, при этом его изменение на единицу даёт относительную погрешность всего около 0,002% от конечного значения. Такая точность представления чисел (и даже на два порядка худшая) вполне приемлема для подавляющего большинства практических задач. Но тогда остро встаёт вопрос о диапазоне представления чисел и порядке вычислений. Например для целочисленного умножения исходные числа не могут быть больше 377 каждое, а значит, точность их представления будет не выше 0.4%. Это пока ещё допустимая точность для большинства задач управления объектами, особенно если учесть, что точность многих датчиков на порядок ниже. Но при операциях с такими числами возможна очень большая потеря точности, особенно при делении. Когда делимое и делитель одного порядка, погрешность результата в целых числах будет не меньше 10%. Поэтому при решении вычислительных задач в целых числах первостепенное внимание надо уделять порядку вычислений: вначале выполнять умножение, а потом деление, заботиться, чтобы не было деления меньшего числа на большее (что приведёт к потере числа в дальнейших вычислениях) и т.п. Общее правило целочисленной арифметики вообще таково: надо стараться, чтобы при умножении и делении как исходные числа, так и результат лежали как можно ближе к границе переполнения (т.е. для двухбайтных чисел без знака - к 65536д), но переноса не происходило. Тем не менее при вычислении таким образом сложных функций, например тригонометрических. число последовательных умножений и делений будет так велико, что потеря точности станет значительной, а время вычислений - соизмеримым с таковым в языках высокого уровня. В этих случаях рекомендуется, когда это возможно, заменять вычисления функций по формулам на их табличное представление. Как это сделать? Предположим, необходимо вычислять значения синуса угла в диапазоне от 0 до 90º. Если точность представления аргумента не выше 1º, таблица значений такой функции займёт всего 90 машинных слов (а если пойти на некоторое снижение точности, то 90 байт). Зная, что диапазон значений данной функции лежит в пределах от 0 до 1, сопоставим максимуму функции число 65535, а минимуму - 0. Промежуточные значения при заданном аргументе легко вычисляются умножением синуса на число 65535 и подставляются в таблицу. Например, синус 30 при таком представлении будет равен 0.5 * 65535 = 32768 (округлённо). Вряд ли программа вычисления синуса с точностью до пятого знака уложится в такой же объём памяти, как таблица, а о сравнимом быстродействии не приходится и говорить. Если же необходимо большее число градаций аргумента, то не стоит увеличивать в десятки раз объём таблицы, а лучше всего, особенно для такой функции, как синус, прибегнуть к линейной интерполяции. Таким путём в большинстве случаев удаётся избегнуть громоздких и длительных вычислений по формулам. Если же, как часто бывает, нужен не весь диапазон представления аргумента, а только его часть (например, вычисление радиуса разворота самолёта в зависимости от крена, допустимое значение которого не превышает 30º), то таблица станет ещё короче либо может быть повышена точность. Не следует забывать и об элементарных преобразованиях функций, известных ещё из курса средней школы, которые часто позволяют выразить одну функцию через другую и тем самым упростить вычисления или сократить количество функций, вычисляемых таблично.
Что же касается генерации случайных чисел. то тут есть очень простой и на первый взгляд неочевидный выход. Что представляет собой ПЗУ БК-0010, если не последовательность чисел, с точки зрения пользователя случайную? Специальные исследования показали, что если выбирать последовательные числа из ячеек ПЗУ, то после несложных преобразований их ряд хорошо удовлетворяет равномерному закону распределения (значительно лучше, чем, например, генератор случайных чисел Фокала). О возможностях же использования в качестве генератора случайных чисел системного таймера уже говорилось в разделе, посвящённом системным регистрам.
Итак, хотя ассемблер - это язык, явно лучше приспособленный для логической, чем для математической, обработки информации, всё же, прибегая к специальным приёмам, вполне можно без потери присущего ему быстродействия выполнять с его помощью и вычисления.
«Стандартные» подпрограммы
Рассмотрим несколько подпрограмм на ассемблере, имеющих практическое значение и предназначенных для работы с числами. Для наиболее придирчивых (тех, кто любит искать чужие ошибки и неточности) автор сообщает, что эти программы (как и вообще все, приводимые в тексте) написаны им лично. (Хотя для простейших программ авторство - дело сомнительное и «скользкое»: уж больно велика вероятность попасть на проторённый путь, и тебя в два счета обвинят в плагиате... Да и вообще, существует ли авторское право на простейшие приёмы программирования? Автор склонен думать, что нет, так как оно не имеет смысла - каждый легко может «переоткрыть» эти приёмы сам, не «списывая» у другого. Тем не менее автор охотно выслушает замечания читателей.) Все эти программы тщательно проверены и отлажены на БК-0010, и, если не возникнет ошибок при их перепечатке, а затем при вводе с клавиатуры на ЭВМ читателя, все они должны работать. Как с наиболее простого примера, начнём, пожалуй, с генератора псевдослучайных чисел.
; Подпрограмма генератора ; случайных чисел ; Первое обращение к программе - RNZ, ; последующие - RND ; R4 - выход случайного числа, ; R5 - счётчик адреса RND: ADD (R5)+,R4 ; Получение SWAB R4 ; очередного ADD 7(R5),R4 ; псевдослучайного DEC R4 ; числа ADD 15(R5),R4 ; в регистре R4 CMP #137750,R5 ; Конец ПЗУ? BLO RNZ ; Да - перезапуск RET ; Выход RNZ: MOV #100000,R5 ; Адрес начала ПЗУ BR RND ; Генерация
Программа извлекает содержимое трёх ячеек ПЗУ, «перемешивает» полученные числа и выдаёт результат в регистре R4. Используемый способ «перетасовки» трёх чисел для получения псевдослучайной последовательности выбран в результате опытной проверки полученных рядов чисел на равномерность и случайность распределения. Программа даёт непериодический ряд из примерно 8000 чисел, распределённых в диапазоне 0...177777. Обращение по метке RNZ необходимо для задания адреса начала ПЗУ, а также может использоваться при отладке, так как даёт всё время одно число.
Так же проста и программа умножения:
; Подпрограмма умножения ; 8-разрядных чисел ; R2-множимое, R3-множитель, ; R4-результат, R5-счётчик итераций MUL: MOV #10,R5 ; Цикл из 8 итераций CLR R4 ; Очистить ; аккумулятор результата 1: ASR R3 ; Очередной разряд ; множителя BCC 2 ; Если 0 - дальше ADD R2,R4 ; Иначе прибавить множимое 2: ASL R2 ; Сдвиг множимого на ; один разряд SOB R5,1 ; Если не конец - в цикл RET ; Выход
Алгоритм этой программы построен совершенно аналогично правилу умножения «в столбик» для двоичных чисел и. видимо, в пояснениях не нуждается.
Программа деления[19] уже заметно сложнее:
; Подпрограмма деления ; 16-разрядных чисел ; R2-делимое, R3-делитель, R4-частное, ; R5-счётчик итераций DIV: CLR R5 ; Очистить счётчик CLR R4 ; и аккумулятор ; результата 1: INC R5 ; + итерация ASL R3 ; Делитель сдвинуть ; влево BCC 1 ; Если не 1 - продолжать ; искать начало делителя 2: ROR R3 ; Возврат старшего ; разряда 3: INC R4 ; + 1 в разряд ; частного SUB R3,R2 ; Вычитание делителя BCC 3 ; Если не конец - ; продолжать ADD R3,R2 ; Возврат к ; предыдущему DEC R4 ; значению делимого ASL R4 ; Следующий разряд ; частного SOB R5,2 ; В цикл итераций ROR R4 ; Шаг назад на ; один разряд RET ; Выход
В строках 3...5 (имеются в виду номера строк по порядку, а не метки) происходит сдвиг делителя влево до первой единицы (т.е. до начала его значащей части) и одновременно определяется число итераций (последовательных вычитаний и сдвигов), необходимых для деления. Оно равно разности 16д и числа значащих разрядов делителя. Затем производится вычитание делителя из делимого, начиная со старших разрядов последнего, а когда результат становится отрицательным, происходит очередной сдвиг и вычитание продолжается в более младшем разряде. Количество вычитаний, с учётом сдвига по разрядам, и есть частное. Остаток от деления сохраняется в R2. При желании можно по остатку произвести округление результата до целого, сравнив его с 1/2 делителя. Эту программу можно усовершенствовать и в другом направлении, например ввести защиту от деления на ноль. (Использованный алгоритм деления весьма распространён и носит название способа с восстановлением остатка.)
Надо отметить, что имеются частные случаи, когда нет нужды прибегать к столь сложным алгоритмам. Например, если заранее известно, что делимое и делитель будут отличаться не более чем на порядок, совсем не требуется производить последовательные вычитания со сдвигом. Вполне достаточно просто организовать вычитание до первого переноса. Программа при этом получится настолько простой, что её нет необходимости здесь приводить. То же относится и к программе умножения, если множитель не превышает 10-20. Но необходимо помнить, что в неблагоприятных случаях такие простейшие программы работают очень плохо, например деление числа 177777 на 2 займёт около 0.5 с[20].
Однако всё это - сами арифметические действия, а как ввести исходные данные с клавиатуры или увидеть результат на экране? Для этого тоже можно предложить ряд программ, например для ввода восьмеричного 6-разрядного числа с клавиатуры и вывода такого же числа на экран.
; Подпрограмма ввода восьмеричных ; чисел с клавиатуры ; Окончание ввода числа - клавиша ; "ВВОД", число - в R4 INP: CLR R4 ; Очистить буфер числа 1: EMT 6 ; Ввод очередного ; символа EMT 16 ; Выдать символ на ; экран CMPB #12,R0 ; Символ "ВВОД"? BEQ 2 ; Да - выход ASL R4 ; Иначе - продвинуть ASL R4 ; предыдущее число ASL R4 ; в старший разряд SUB #60,R0 ; Перевести код ; символа в число ADD R0,R4 ; Записать число в ; буфер BR 1 ; Ввод следующего ; символа 2: RET ; Выход
Программа эта простейшая и поэтому не лишена недостатков. Например, она не защищена от ввода нецифровых символов, не допускает исправлений, при вводе более чем шести символов действительны только последние шесть и т.д. При желании программу нетрудно дополнить всеми необходимыми сервисными функциями.
; Подпрограмма вывода на экран ; восьмеричных чисел в коде КОИ-8 ; Исходное число - в регистре R4 OUT: CLR R0 ; Очистить буфер цифры MOV #6,R5 ; Цикл чтения 6 ; разрядов BR 2 ; Чтение старшего ; разряда числа 1: CLR R0 ; Очистить буфер цифры ROL R4 ; Переслать 3-разрядное ROL R0 ; двоичное число, ROL R4 ; определяющее ; очередную ROL R0 ; восьмеричную цифру, 2: ROL R4 ; в буфер ROL R0 ; цифры ADD #60,R0 ; Перевести число в ;код КОИ-8 EMT 16 ; Выдать цифру на экран SOB R5,1 ; Цикл чтения разрядов RET ; Выход
Эта программа также может быть усовершенствована. Например, иногда необходимо вместо незначащих нулей впереди числа выдавать пробелы, вместо числа без знака выводить число со знаком, интерпретируя его код как дополнительный, и т.п. Ценой некоторых (но не слишком больших!) усилий программиста все эти задачи могут быть решены. Часто нужен ввод и вывод не восьмеричных, а десятичных чисел. Реализовать такие программы несколько сложнее, но также вполне возможно. Вообще, как говорится, «лиха беда начало», и автор ничуть не сомневается, что читатели очень быстро добьются в программировании на ассемблере значительно больших успехов, чем он сам.
Можно отметить, что программы, вполне аналогичные двум последним, имеются в ПЗУ БК-0010. Например, подпрограмма ввода восьмеричного числа с клавиатуры имеется в ПЗУ монитор-драйверной системы, адрес обращения к ней - 100472. Она заносит число в регистр R5 и отличается от приведённой программы INP тем, что ввод любого «невосьмеричного» символа сбрасывает набранное число, так что набор нужно повторять. Эту подпрограмму можно применять во всех случаях, ведь ПЗУ МДС в БК-0010 имеется всегда. А вот подпрограмма вывода восьмеричного числа, хранящегося в R4, имеется только в тест-ПЗУ по адресу 163220 и при его отсутствии (без блока МСТД на БК-0010.01) программы с её использованием работать не будут. Ясно, что её применение не всегда корректно.
В результате разработки (обычно по мере необходимости) различных программ у каждого программиста не только появляются свои излюбленные приёмы, «фокусы» и прочее, определяющие так называемый «почерк» программиста (чего почти лишены программы на языках высокого уровня), но и постепенно накапливаются библиотеки стандартных подпрограмм, подобных приведённым выше. Они могут быть использованы в разных программах по мере надобности, а хранить их лучше на МЛ отдельно, в виде листингов. Возможности ассемблера «МИКРО.10К» (других - в меньшей степени) позволяют при необходимости «догрузить» нужный программный модуль и «переставить» его в требуемое место программы, тем самым избавляя программиста от труда вводить его заново. Поэтому рекомендуется снабжать каждый такой модуль-подпрограмму уникальной меткой-именем, как это и сделано в наших примерах[21].
Контрольные вопросы и задания
- Принесло ли вам пользу знакомство с ячейками системной области БК-0010?
-
Каждый отвечает так, как считает нужным.
-
- Напишите программу генератора случайных чисел, использующую системный
таймер БК-0010. Выдача каждого следующего числа на экран должна происходить
при нажатии клавиши, причём использовать EMT
6 для ожидания нажатия нельзя - найдите другой
прием. Диапазон выдаваемых случайных чисел - от 0
до 377[22].
-
OUT=163220 ; Адрес подпрограммы ; вывода RT1=177706 ; Регистры RT2=177710 ; системного RT3=177712 ; таймера SWU=177716 ; Регистр системных ; устройств MOV #377,RT1 ; Исходное ; число счётчика MOV #20,RT3 ; Множитель "xl" 0: BIT #100,SWU ; Клавиша ; нажата? BNE 0 ; Нет - ждать 1: BIT #100,SWU ; Клавиша ; отпущена? BEQ 1 ; Нет - ждать MOV RT2,R4 ; Извлечь число ; из счётчика CALL OUT ; Выдать число ; на экран BR 0 ; Программа ; зациклена END
Обратите внимание, что все адреса ПЗУ и системных регистров заданы с помощью операторов прямого присваивания.
-
- 3 .Какой приём вы можете предложить для перевода восьмеричного числа
в десятичное?
-
Простейший приём такой. Нужно вычислить значения «круглых» десятичных чисел - 10, 100, 1000, 10000 в восьмеричном представлении и затем вычитать их из исходного числа, начиная с самого большого для одного регистра - с 10000, подсчитывая каждый раз количество вычитаний, пока ещё нет переноса. Это количество, переведённое в код КОИ-8 (если его нужно выдать на экран), и будет десятичным разрядом. Затем нужно восстановить остаток и продолжить вычитание уже следующего числа, например 1000, и так до 1. (Восьмеричные эквиваленты приведённых десятичных чисел- 23420, 1750, 144, 12.)
-
- Написать программу, обеспечивающую перекодирование десятичного числа
из его текстовой записи (т.е. строки символов «0»..«9»,
вводимых один за другим с клавиатуры) в числовое содержимое регистра
R2.
-
PAC: CLR R2 ; Очистить ; счётчик 0: EMT 6 ; Очередной EMT 16 ; символ CMPB #12,R0 ; Окончание ; ввода? BEQ 1 ; Да! SUB #60,R0 ; ASCII-код -> ; в цифру BCS 2 ; Код меньше ; нуля CMP R0,#12 ; Цифра? BCC 2 ; Нет, код ; больше девяти ASL R2 ; Число ; умножаем на 2 MOV R2,-(SP) ; Сохраняем ASL R2 ; Умножено на 4 ASL R2 ; Умножено на 8 ADD (SP)+,R2 ; Х*8+Х*2 =Х*10 ADD R0,R2 ; Прибавить к ;счётчику BR 0 ; Продолжать 2: CLR R2 ; Если ; некорректно, то 0 1: RET ; и выход
-
Трансляция и компоновка
В своё время мы уже кратко познакомились с тем, как подготовить программу к работе - оттранслировать и скомпоновать её, и обещали ещё раз вернуться к этой теме. Так мы и сделаем, но напомним, что наше описание базируется на ассемблер-системе «МИКРО.10К» и все приводимые далее адреса и директивы относятся прежде всего к ней. Для прочих версий они могут отличаться от указанных, хотя принципы работы те же самые.
Пусть у нас написан текст программы на языке ассемблера. В его состав могут входить:
- операторы прямого присваивания,
- метки,
- операторы,
- операнды с различными способами адресации,
- псевдооператоры с наборами символов,
- комментарии.
Весь этот чрезвычайно разнородный материал (размещённый в памяти по адресам 17001...37000) ассемблер должен превратить в программу в машинных кодах, готовую к исполнению, - загрузочный модуль, - «собрать» из них программу. Отсюда и название системы: «ассемблер» означает «собиратель». В общих чертах мы уже знаем, как это делается, но рассмотрим этот процесс подробнее.
Обычно любой ассемблер выполняет трансляцию за два прохода (или просмотра) исходного листинга программы. Первый проход называется трансляцией и может сам состоять из ряда операций, но проще рассматривать его как одно целое.
Запуск транслятора осуществляется из его встроенного монитора по директиве «СО». В процессе трансляции производится перевод текста программы в машинные коды и присваивание меткам определённых адресов. По мере перевода текста в машинные коды производится запись последних в память ЭВМ (начиная с адреса 13000), и встречающиеся по ходу трансляции метки располагаются уже не просто в определённых строках программы, а по определённым физическим адресам. Эти адреса (вместе с именами меток в коде RADIX-50) заносятся в таблицу меток, формируемую с адреса 40000 «вниз», т.е. в сторону меньших адресов. Если меток очень много, таблица может стать слишком длинной и затереть при трансляции конец текста программы раньше, чем он будет прочитан транслятором. Естественно, при этом возникает ошибка и трансляция прекращается. Избежать чрезмерного роста таблицы можно путём широкого использования локальных меток, так как таблица локальных меток формируется отдельно и существует в памяти только до появления очередной обычной метки.
Если всё-таки таблица меток затирает текст (его на всякий случай перед трансляцией нужно всегда записывать на МЛ), то такую программу оттранслировать невозможно, её необходимо либо разделить на части, либо перейти в режим «РП». Любая версия ассемблера «МИКРО.10К» рассчитана на работу в этом режиме (кроме редактирования текста), а в последних версиях - «МИКРО.10К-РП» и «МИКРО.10-01К» в режиме «РП» работает и редактор. В этом режиме можно транслировать также тексты программ, составленные из нескольких частей, «сливая» их друг с другом путём догрузки директивой «LF» Естественно, что при этом оператор END должен стоять только в конце последнего блока, а общая длина текста может достигать 50000. Во многих случаях такое слияние текстов программ значительно удобнее компоновки через объектные модули, о котором речь пойдёт дальше. Чтобы ассемблер нормально работал в режиме «РП», его необходимо либо запустить в этом режиме с адреса 1000. либо, перейдя в режим «РП» в мониторе, дать команду «RS» (версия «МИКРО.10К-РП» в этом не нуждается - достаточно перейти в режим «РП» в мониторе ассемблер-системы). Текст программы в памяти при этом, конечно, будет уничтожен. В режиме «РП» таблица меток формируется с адреса 70000.
В результате трансляции (первого прохода) образуется так называемый объектный модуль. Он представляет собой программу в машинных кодах, в которую занесено всё необходимое, кроме некоторых операндов, под которые ячейки памяти только зарезервированы. Дело в том, что при первом проходе транслятор ещё «не знает» реальных физических адресов меток, ведь таблица только формируется, поэтому и не может записать операнды, обращение к которым требует знания этих адресов. Такие операнды, ячейки под которые зарезервированы (туда заносятся нули), транслятор отмечает в выдаваемой на экран по окончании трансляции таблице меток инверсией, с указанием адресов зарезервированных ячеек и имён меток, входящих в состав операнда. Наличие в таблице после трансляции инверсных меток однозначно свидетельствует, что программа неперемещаема и нуждается в компоновке - это второй проход транслятора. Объектный модуль. полученный при трансляции, может быть после неё записан на МЛ директивой «SL», при этом вместе с ним записывается и таблица меток. Ассемблер даёт возможность загрузить с МЛ и скомпоновать вместе несколько объектных модулей, что позволяет получить (в режиме «РП», разумеется) загрузочный модуль длиной до 16 кб. Но пока рассмотрим случай, когда у нас один модуль.
Итак, объектный модуль в памяти, таблица меток - на экране (если таблица меток занимает более 24 строк в обычном режиме или более четырёх строк в режиме «РП», её выдача на экран производится по частям, с остановками до нажатия любой клавиши). Раз в таблице есть инверсные метки, значит, программа нуждается в компоновке. При компоновке не только производится вычисление и запись в загрузочный модуль недостающих операндов. но и задание адреса загрузки, т.е. того адреса, по которому программа будет загружаться и работать в дальнейшем. Этот адрес может быть задан любым, ассемблер внесёт соответствующие поправки во все адреса меток программы, как бы «переместив» её в памяти (в действительности загрузочный модуль остаётся на месте, по адресу 13000), поэтому наш ассемблер называется перемещающим. Директива «LL» задаёт компоновку по умолчанию - по последнему указанному ранее адресу (после запуска ассемблера это адрес 1000), а директива «LS» запрашивает новый адрес компоновки у пользователя. После окончания компоновки инверсных меток в таблице (она выдаётся на экран заново) не должно остаться, а если они всё же есть, значит, в программе имеются ошибки. Это либо неопределённые метки (т.е. обращение к метке есть, а её самой нет), либо обращение к локальной метке «через обычную» (в скобках заметим, что такое обращение «вперёд» допустимо, но лучше его избегать. это только запутывает программу).
После того как получен загрузочный модуль (в таблице меток нет инверсных имён), его можно записать на МЛ директивой «SA». При этом учтите, что располагается он с адреса 13000 независимо от заданного адреса компоновки, с этим начальным адресом он и будет записан на МЛ. При загрузке такой программы для работы нужно всегда явно задавать адрес загрузки, если он отличается от 13000, а так как удобнее грузить программу «по умолчанию», то лучше сразу переслать её директивами МСД по нужному адресу, а потом уже записать на МЛ, для чего надо выйти из ассемблера, нажав клавишу «СТОП». Длина загрузочного модуля выдаётся при трансляции и компоновке вместе с таблицей меток, адрес тоже известен - 13000, так что переслать программу по необходимому адресу и затем записать её на МЛ не составит труда, только для этого надо выйти в МСД (значит, блок МСТД должен быть заранее подключён, а способ перехода в МСД, если вы загружали «МИКРО» из ПМ, уже приводился).
Можно также сразу запустить загрузочный модуль на исполнение директивой «RU». при этом управление просто передаётся по адресу 13000. Учтите только, что для этого компоновка должна быть произведена именно по этому адресу, а ассемблер в памяти вполне может быть испорчен («затёрт») работающей программой. Такой приём (прямой запуск) часто используется для отладки, иногда специально для этого даже изменяют некоторые параметры программы, чтобы она могла работать по адресу 13000 без затирания ассемблер-системы. Это действительно очень удобно: запустив программу по «RU» и проверив результаты её работы, можно вернуться в ассемблер-систему по «повторному входу» (адрес запуска 1002), войти в редактор, внести в текст изменения, снова оттранслировать и скомпоновать, вновь запустить и т.д., пока работа программы не будет удовлетворительной. Затем можно внести в текст программы окончательные изменения для работы по нужному адресу и скомпоновать её, задав этот адрес. Такая методика отладки особенно удобна, если длина загрузочного модуля невелика (до 4000) и он не затирает при трансляции начало текста программы, - это не сложнее отладки программ на Бейсике или Фокале.
А как быть, если программа длиннее и её текст не помещается в памяти ни в обычном режиме, ни в «РП»? Тогда её нужно разделить на несколько частей, по возможности таких, чтобы их можно было отладить отдельно. После проверки каждой части её транслируют и записывают объектный модуль на МЛ. Каждая часть при этом должна содержать в начале необходимые операторы прямого присваивания (относящиеся к меткам, используемым в ней), а в конце оператор END.
Когда получены и записаны на МЛ все необходимые объектные модули программы, даём директиву «LA», указываем адрес компоновки и загружаем первый модуль. В дальнейшем грузим остальные модули по команде «LI». Каждый вновь загруженный модуль компонуется с уже имеющимися в памяти. После загрузки всех модулей (непременно в том порядке. в котором они должны входить в программу!) загрузочный модуль готов, его можно записать на МЛ или запустить. Перед компоновкой модулей с МЛ нужно перезапустить ассемблер командой «RS».
Ошибки при программировании на ассемблере
Синтаксические ошибки в тексте программы выявляются обычно на этапе трансляции. При этом трансляция прекращается, на экран выдаётся строка с ошибкой и запрос: «Е,С?», т.е. «перейти к месту ошибки или продолжить?». Команда «С» вызывает продолжение трансляции независимо от того, что строка с ошибкой оттранслирована не будет. Это может потребоваться для выявления остальных ошибок или для иных целей. В конце такой трансляции таблица меток не выдаётся. Команда «Е» вызывает переход в редактор, при этом курсор указывает на место ошибки. Если это «ошибка 3» (велика длина перехода), то указывается не метка, при переходе к которой допущено превышение длины перехода в SOB, BR или другом операторе ветвления, а сам оператор, т.е. источник обращения. В некоторых версиях «МИКРО.10К» была введена «ошибка 15» (затирание текста программы при трансляции). Но нужно сказать, что эта ошибка только констатирует факт, но не защищает текст от затирания, так как она выдаётся после трансляции последнего оператора и при этом часть текста неизбежно затирается. Но если это был обычный оператор, затёртой окажется небольшая часть текста, которую легко восстановить. Если же последним был, например, оператор .+10000, то будет утерян текст длиной 10000, несмотря на останов по ошибке. Обычная реакция на эту ошибку - команда «С», при этом трансляция идёт нормально до конца. В версиях «РП» «ошибка 15» исключена, зато добавлена «ошибка 12» - переопределение локальной метки, которая раньше не индицировалась и доставляла много хлопот.
Не совсем приятная ошибка - обращение к неопределённой локальной метке. При этом в таблице меток возникает путаница - появляются инверсные метки без имени, а только с адресом, или метки с именами, которых нет в тексте. Но если знать эти характерные признаки данной ошибки, найти её не так сложно.
Сложнее дело обстоит с логическими ошибками, когда программа формально написана правильно, но работает не так. как ожидалось. В этом случае, если обычный просмотр текста и проверка алгоритма не выявляют ошибок, следует разбить программу на функциональные блоки и проверять их по отдельности. Частая ошибка - неправильное ветвление программы. Она может быть вызвана непониманием логики работы программы, неправильным оператором (например, применение оператора «со знаком», когда нужен «без знака») или ошибками при сравнении операндов. На последнем случае стоит остановиться подробнее.
Когда ветвление задаётся после оператора сравнения, то оно может происходить неправильно по следующим причинам:
- неправильный порядок записи операндов (нужно помнить, что при сравнении из первого операнда вычитается второй, а при вычитании - наоборот);
- сравнение слов, когда надо сравнивать байты, и наоборот;
- искажение одного из сравниваемых операндов за счет распространения знака после выполнения оператора MOVB с одним из регистров;
- использование оператора ветвления с иной, чем нужно, логикой работы, например BLO вместо BLOS, BGT вместо BGE, BEQ вместо BNE и т.п.;
- недооценка возможного диапазона представления операндов, например когда при возрастании операнда происходит переполнение и изменение знака.
Кроме перечисленных возможны, конечно, и другие причины, но эти - самые распространённые. Не менее частая причина ошибок ветвления - передача управления «не туда», т.е. когда соответствующая метка находится не на том месте, где должна бы быть. Например в цикле может всё время повторно заноситься исходное значение цикловой переменной, обнуляться регистры или повторно задаваться исходный адрес массива и т.д.
Уточнить, как именно происходит ветвление и где программа «зацикливается», а также отследить прочие «сомнительные точки» можно разными приёмами. Нагляднее и проще всего на время включать в текст программы в точках перехода по ветвлению операторы действие которых сразу заметно: HALT, звуковой сигнал, выдача на экран каких-либо символов и пр. Можно временно исключать из текста отдельные строки, «закрывая» их знаком комментариев (поставив в первой позиции символ «;» или вписав в начале строки оператор NOP), можно заменять операторы условного ветвления на безусловные и т.п.
Можно прибегнуть и к помощи отладчика. При пошаговой работе программы можно наблюдать содержимое регистров и т.д. Особенно интересен «ОТЛАДЧИК.К» С.А. Кумандина, который позволяет, кроме регистров, задать индикацию ещё до пяти произвольных ячеек памяти, имеет RADIX-дамп и прочие сервисные функции. При работе с любым отладчиком затруднение нередко вызывает то, что командные прерывания EMT и TRAP отрабатываются как одна команда. Это происходит потому, что при прерывании ССП заменяется и T-разряд сбрасывается. Как сделать эти команды «видимыми» при отладке, мы уже обсуждали ранее: чтобы посмотреть, что делается «внутри» прерывания, нужно предварительно записать во втором слове вектора данного прерывания ССП с установленным T-разрядом. Но, вообще говоря, возможности любого отладчика довольно ограниченны и эта «техника» (во всяком случае, по мнению автора) более подходит для того, чтобы «копаться» в чужих программах, а не для отладки собственных.
Когда программа в общих чертах отлажена и вроде бы работает так, как хотелось, ещё нельзя считать работу оконченной. Программу необходимо протестировать. Если она не очень сложная, нужно задавать такие исходные данные, чтобы (с учётом логики обработки) были пройдены по возможности все ветви программы, и перебрать все возможные комбинации данных. Сделать это тому, кто сам написал программу, не так уж сложно, как кажется. Если трудно представить себе последовательность обработки данных в сложной программе, можно ограничиться заданием трёх значений для каждого вида входной информации: двух крайних и одного произвольно выбранного в середине диапазона (речь идёт не только о числовых данных, но и, например, о директивах, позициях меню и т.п.). Если диапазон входных данных ограничен, необходимо задать ещё как минимум два их значения за пределами диапазона с обеих сторон. Но, несмотря на все ухищрения, исчерпывающая проверка сложной программы невозможна в принципе и зачастую только после длительной эксплуатации выявляются ошибки, не обнаруженные в процессе отладки. Такая ошибка всегда очень неприятна, прежде всего потому, что программист уже успел забыть детали реализации алгоритма, ему трудно снова «вжиться» в программу и сообразить, что именно привело к ошибке. В этих случаях нельзя переоценить значение сохранения листинга с подробными комментариями, особенно если данная программа не единственная разработка программиста. Поэтому лишние пять минут, потраченные на написание комментариев, могут в будущем сберечь вам многие часы и даже дни работы. Вероятность же такой ситуации очень велика, ибо давно известное шуточное правило гласит: «Всякая отлаженная программа содержит как минимум одну ошибку. Исключением является только программа, состоящая из единственного оператора END».
Позиционно-независимое программирование
Обычно программа, написанная на языке ассемблера, предназначена для работы в определённой зоне адресов. Но есть программы, которые просто обязаны работать по любым адресам: это отладчики, дизассемблеры, супервизорные драйверы и прочие «инструменты». нужные не сами по себе, а для работы с другими программами. При этом заранее неизвестно, где будет расположена программа, нуждающаяся в отладке или дизассемблировании, поэтому мы вынуждены размещать инструментальную или системную программу «на свободном месте». Как добиться перемещаемости программ? Для этого следует придерживаться ряда принципов, носящих название принципов позиционно-независимого программирования[23].
- Все адреса переходов и обращений к тексту программы надо задавать только путём относительной адресации, в виде меток.
- Все буферы данных внутри программы нужно выделять тоже путём относительной адресации, по меткам, т.е. делать их перемещаемыми.
- Ко всем ячейкам ПЗУ и системной области необходимо обращаться только по абсолютным адресам или по меткам, которые определены оператором прямого присваивания.
- Не выделяйте в ОЗУ (кроме системной области) буферы и ячейки с абсолютными адресами. так как при размещении программы в этой зоне она может быть затёрта собственным буфером.
- Если необходимо определить физический адрес обращения к ОЗУ (или к ячейкам в самой программе), например при задании векторов прерываний, адресов текстовых сообщений и т.п., то вычислять эти адреса нужно пользуясь псевдооператором «.@».
- Старайтесь чаще использовать регистры общего назначения, стек и косвенную адресацию через регистры и метки, что обеспечивает независимость от адресов размещения программы.
Перечень этих принципов читатель может продолжить самостоятельно.
Признаком перемещаемости является то, что программа не нуждается в компоновке. Т.е. таблица меток, выданная транслятором (после первого прохода), не содержит инверсных имён (хотя это и не абсолютная гарантия перемещаемости!). Перемещаемые программы обычно занимают заметно больше места в ОЗУ, чем неперемещаемые, и несколько сложнее по структуре, поэтому стремление делать перемещаемыми все программы вряд ли оправдано.
Особый случай - размещение программы в ПЗУ. Обычной перемещаемости здесь недостаточно, нужно ещё позаботиться о том, чтобы внутри программы не было никаких переменных. буферов данных и т.п. Кроме того, если такая программа рассчитана на работу как в ОЗУ, так и в ПЗУ и предназначена для работы в реальном времени, необходимо учитывать, что одна и та же программа при размещении в ПЗУ работает на 10-30% быстрее, чем в ОЗУ пользователя. Например, для драйвера магнитофона эта разница может оказаться критической, если не предусмотрены специальные меры, обеспечивающие приемлемый эффект при изменении скорости работы Лучше всего, чтобы после запуска программа сама определяла, где она находится. и в соответствии с этим организовывала свои буферы данных, самозащиту, регулировала скорость работы и т.п.
Схемотехника БК-0010
У определённой части пользователей персональных компьютеров существует устойчивый интерес к схемотехнике и электронике. Оправдан ли этот интерес применительно к БК? Стоит ли изучать схемотехнику этого компьютера, и если да, то для чего?
Во-первых, любая техника рано или поздно выходит из строя. Хотя сервисная сеть по ремонту БК-0010 и существует, воспользоваться её услугами иной раз довольно сложно (скажем, для пользователей, живущих на периферии). Зная же основы схемотехники БК, можно попытаться сделать это самостоятельно[24]. Однако сразу следует оговориться, что начинающим пользователям не стоит даже пытаться это сделать. Такая возможность открыта лишь для опытных радиолюбителей и для профессионалов в области микросхемотехники. Помимо теоретических знаний, отсутствующих у подавляющего большинства пользователей БК (наивно думать, что эта статья позволит приобрести их в нужном объёме), имеется ещё масса «тонкостей» чисто практического свойства, известных лишь тем, кто работал с микропроцессорной техникой. Существует также множество приёмов проверки и отладки микроэлектронных схем, научить которым «заочно» вообще невозможно, их каждый приобретает самостоятельно лишь после многих лет практической работы: опытный ремонтник «чувствует» неисправность, даже порой не отдавая себе отчёта, каким образом это у него получается. Итак, в смысле возможностей ремонта БК эта часть книги полезна только тем, кто может ею воспользоваться. Всегда помните древнейшую поговорку: «То, что позволено Юпитеру, не позволено быку» - и правильно оценивайте свои силы. То же самое замечание в полной мере относится и к случаю подключения к БК-0010 дополнительных устройств (дисковода, принтера, дополнительной памяти и т.п.)[25].
Во-вторых, этот материал может оказаться полезным при изучении других ЭВМ с аналогичной организацией («Электроника-60» или ДВК), станков с числовым программным управлением и т.п.
И наконец, кто знает, не послужит ли для кого-либо этот раздел стимулом, который приведёт в дальнейшем к «большой электронике»? Словом, данный материал будет полезен «для общего развития».
Было бы безнадёжным пытаться изложить схемотехнику БК-0010 «корректно», т.е. с введением и объяснением терминологии, описанием работы отдельных схем, узлов и т.п. - это потребовало бы нескольких книг. Поэтому всех интересующихся подробностями приходится отослать к обширной литературе по радиоэлектронике (список литературы см. в №1 за 1994 г.). После всех этих оговорок приступим к описанию схемы БК-0010. Оно будет основано на принципиальной схеме компьютера, опубликованной в журнале «Информатика и образование», №4 за 1990 г., с. 74-77, а также в журнале «Персональный компьютер БК-0010 - БК-0011М», №2 за 1994 г. Несмотря на наличие в ней значительного количества неточностей и ошибок (впрочем, многие из них исправлены в схеме, опубликованной в №2 за 1994 г.), это единственная доступная пользователю схема БК-0010. Об отличиях схемы БК-0010.01 мы будем упоминать по мере возможностей, они в основном незначительны и непринципиальны.
БК-0010 выполнена на основе микропроцессорного набора серии К1801. Перечень микросхем (МС) этого типа, выполненных по так называемой n-МОП технологии, включает помимо нескольких видов ЦП набор специализированных микросхем-контроллеров. Все они изготовлены на базе одной микросхемы, групповое название которой - К1801ВП1, и отличаются связями логических элементов - своего рода «микропрограммой», зашитой в них в процессе изготовления. Каждому контроллеру присвоен свой порядковый номер (например: К1801ВП1-014, К1801ВП1-037, К1801ВП1-128 и т.д.). Такая технология помимо узкой специализации и, следовательно, высоких функциональных качеств каждого контроллера (во всяком случае таковы они по мнению разработчиков, пользователи же с этим мнением не всегда согласны) позволяет легко и без больших затрат расширять их набор почти неограниченно.
Все функциональные узлы (МС, блоки и т.п.) связаны в микро-ЭВМ линиями связи, называемыми в совокупности системной магистралью (СиМ). Системная магистраль имеет 16 линий «адрес-данные» (шина АД) и ещё несколько «служебных линий» (СЛ) для передачи специальных сигналов. Обмен по СиМ производится асинхронно. При таком виде обмена каждое из входящих в систему устройств может быть включено в СиМ в произвольном месте и работает самостоятельно, в своём временном режиме («ритме»). Все устройства, подключённые к СиМ, делятся на активные (способные занимать и освобождать магистраль «по своей воле») и пассивные (вынужденные ждать, пока магистраль им будет предоставлена активным устройством). Другими словами, активным считается устройство, способное самостоятельно инициировать и осуществлять обмен по СиМ, а пассивным - устройство, работающее под «надзором» активного. Активным устройством (АУс) в нашем компьютере является только ЦП, все остальные устройства в его составе - пассивные. Каждое из пассивных устройств (ПУс) имеет в общем адресном пространстве ЭВМ свой адрес (или группу адресов), по которому к нему может обратиться АУс.
Как же происходит обмен информацией по СиМ? В начале цикла обмена магистраль «занята» АУс, в нашем случае ЦП. Он выставляет на шину АД СиМ адрес того ПУс, к которому хочет обратиться. Если это системный регистр, то этот адрес будет для него единственным, который вызывает реакцию, а если это, например, ОЗУ или ПЗУ, то при обращении ко всей области адресов, обслуживаемой данным ПУс, его реакция будет одинаковой. Выставив адрес, ЦП выдаёт по одной из СЛ специальный сигнал, называемый «синхроимпульсом активного устройства» (СИА) или сигналом SYNC (синхронизация обмена). Пассивное устройство, получив сигнал SYNC (он подаётся на все устройства ЭВМ одновременно), фиксирует (обычно в специальном регистре) полученный по шине АД адрес. Дальнейший порядок обмена зависит от того, что нужно сделать: передать данные в ПУс (цикл вывод, или запись) или принять их от него (цикл ВВОД, или чтение). В зависимости от этого ЦП почти одновременно с сигналом SYNC выставляет на других СЛ ещё один из двух сигналов - DIN (ввод) или DOUT (вывод). Рассмотрим сначала цикл ввода.
Если принятый по сигналу SYNC адрес соответствует адресу данного ПУс, то оно по отдельной СА выдаёт сигнал ответа, называемой синхроимпульсом пассивного устройства (СИП) или сигналом RPLY. Получив его. ЦП уже знает, что вызов получен адресатом. А что случится, если вызываемое ПУс отсутствует в составе ЭВМ или попросту неисправно? Неужели ЦП будет ждать до бесконечности, и работа ЭВМ на этом бесславно завершится? Конечно нет! ЦП будет ждать ровно столько, сколько положено (а именно 64 такта специальной, подаваемой на него опорной, или тактовой, частоты), а затем выполнит переход по вектору 4, тем самым констатируя, что вызываемое ПУс на запрос не ответило (произойдёт прерывание по зависанию канала связи).
Итак, ответ получен. Что дальше? По-прежнему выбор порядка действий за ЦП, ему «водить» в этой игре. Вернёмся чуть назад. Выдав сигнал DIN, ЦП освобождает шину АД СиМ (снимает сигналы адреса) и. как уже говорилось, ждёт ответа RPLY. А ПУс одновременно с RPLY (или даже чуть раньше) выставляет на шину АД данные (которые, собственно, и требовал от него ЦП). Получив ответ ПУс, ЦП принимает данные, которые ПУс установило на шине АД (записывает их в один из своих буферных регистров) и снимает сигнал DIN, показывая тем самым ПУс, что данные приняты. ПУс в ответ на это освобождает СиМ, снимая сигнал данных и сигнал ответа RPLY. Теперь ЦП наконец-то снимает сигнал SYNC, тем самым завершая цикл обмена. Пока сигнал SYNC был на СЛ СиМ, он свидетельствовал, что идёт обмен, это было как бы предупреждение всем остальным АУс (если они есть в системе): «Не мешать!» При чтении информации ЦП всегда обращается только по чётному адресу (бит 00 слова адреса всегда равен нулю) и записывает в свой буфер только целое слово. Если же нужен байт, то он выделяется из буфера уже по окончании обмена.
А что происходит, если нужно записать что-либо по адресу ПУс? Тогда ЦП вместо сигнала DIN выставляет по другой СЛ сигнал DOUT (вывод), причём до подачи этого сигнала он успевает освободить шину АД от адреса и выставить на ней данные, которые нужно передать ПУс. Получив сигнал DOUT, ПУс не занимает магистраль, а принимает выставленные на ней данные и только затем выдаёт ответ RPLY, который в данном случае означает «данные приняты», после чего ЦП снимает сигнал SYNC (и данные), завершая обмен. Для того чтобы можно было по адресу ПУс записать не только слово, но и байт, в цикле «вывод» работает ещё одна СЛ. по которой в подобном случае вместе с сигналами SYNC и DOUT передаётся специальный сигнал WTBT, свидетельствующий о передаче байта. Естественно, что при этом передаваемый по сигналу SYNC адрес может быть нечётным (в отличие от цикла ВВОД).
Все служебные сигналы передаются по СиМ БК-0010 низким уровнем, т.е. о наличии на соответствующих СЛ СиМ сигналов DIN, DOUT, RPLY. WTBT свидетельствует уровень «логического нуля», а уровень «логической единицы» означает отсутствие сигнала (как говорят, «линия не активна»).
Существуют и другие виды обмена по СиМ (например, «ввод-модификация-вывод»), но мы их рассматривать не будем, как и обмен при наличии других АУс (например, второго процессора или устройства ПДП - прямого доступа к памяти). Не станем мы разбирать и порядок предоставления СиМ процессором по требованию прерывания от ПУс - все эти виды обмена особого интереса для нас не представляют, важно лишь усвоить общие принципы асинхронного обмена. Кроме того, нужно признать, что циклы ВВОД и вывод мы рассмотрели несколько упрощённо, в действительности обмен происходит немного сложнее, но и изложенного будет для неискушённого читателя вполне достаточно.
Теперь ясно, что такая ЭВМ, как БК-0010, может практически неограниченно расширяться путём подключения новых АУс и ПУс. Важно лишь, чтобы все АУс соблюдали «правила игры» и не мешали друг другу во время обмена, а все ПУс имели различные адреса или, если их адреса одинаковы, каким-либо способом включались по очереди. Все необходимые линии СиМ выведены в БК-0010 на специальный разъем МПИ, к которому и могут при необходимости подключаться дополнительные ПУс или АУс. (Это уже используется на практике: к БК-0010.01 подключается дополнительный блок МСТД, причём отключаются установленные на основной плате БК микросхемы ПЗУ интерпретатора языка Бейсик, но зато подключаются установленные в блоке МСТД ПЗУ тестов и интерпретатора языка Фокал.)
★ ★ ★
Теперь приступим непосредственно к рассмотрению схемотехники нашего компьютера. Как уже говорилось, оно будет очень кратким и поверхностным.
Конструктивно БК-0010 относится к семейству одноплатных ЭВМ. Это означает, что все её основные функциональные узлы размещены на одной печатной плате[26]. Тем не менее схема БК может быть условно разделена на два функциональных блока - процессорный и модуль памяти. Начнём с процессорного модуля.
Все узлы нашей микро-ЭВМ имеют общий тактовый генератор. Он выполнен на микросхеме D5 типа К555ЛН2. Частота генератора (12 МГц) стабилизирована кварцевым резонатором. Тактовая частота после деления на 2 (до 6 МГц) триггером D8.1 подаётся на контроллер ОЗУ и дисплея D19 (о котором речь пойдёт дальше), а кроме того, используется для тактирования телевизионного сигнала. Следующий триггер D8.2 делит тактовую частоту ещё на 2. и полученная частота 3 МГц используется для обеспечения работы ЦП типа К1801ВМ1 (D14).
Сигналы прерываний от внешних устройств на процессор поступают через промежуточный регистр D11 типа К155ИР1. Запись в этот регистр также тактируется частотой 3 МГц. Порт ввода-вывода выполнен на четырёх 8-разрядных регистрах D15-D18 типа К589ИР12. Обращение к порту (выбор регистров) ЦП осуществляет специальным сигналом SEL2 через логический элемент D1.6. Такой порядок обращения к внешним устройствам по сравнению с обычным асинхронным обменом значительно упрощает схему (но надо отметить, что внутри ЦП обмен осуществляется как обычно, просто ЦП сам отвечает на свой сигнал, выступая как АУс и ПУс одновременно).
Регистры системных внешних устройств (ввода и вывода) организованы на МС D12, D13 типов К531АП2П и К155ИР1. Обращение к порту системных внешних устройств также производится специальным сигналом ЦП SEL1.
Клавиатура ЭВМ подключена к общей магистрали через специализированную МС типа К1801ВП1-014 - контроллер клавиатуры. Эта МС имеет входы «X» и «Y» для подключения клавиш, образующие двумерную матрицу. При замыкании контактом клавиши одной из шин «X» на одну из шин «Y» от контроллера к ЦП поступает запрос на прерывание от клавиатуры, а принятый код клавиши заносится в регистр 177662. Переключение режимов «РУС»/«ЛАТ» осуществляется по кодам соответствующих клавиш, включённых в общее поле клавиатуры (и непосредственно передаваемых в программу), а переключение «СТР»/«ЗАГЛ» частично организовано аппаратно на триггере D3.1, который выдаёт специальный сигнал на вход ЕС1 контроллера. Клавиши «АР2» («НР»), «СУ» и «ПР» подключены к отдельным входам контроллера, причём «ПР» - через логический элемент D2.1. Клавиша «ПРОБЕЛ» имеет отдельную аппаратную «привязку» к контроллеру на МС D2.2, при её нажатии включается клавиша «0» по регистру «ПР» (выдающая код пробела). Такое включение позволило выделить для символа «ПРОБЕЛ» отдельную клавишу, на что контроллер клавиатуры изначально не был рассчитан, и выдавать код пробела независимо от текущего режима клавиатуры. Контроллер имеет специальные антидребезговые цепи на резисторах R3, R4 и конденсаторах С3, С4. К сожалению, работа этих цепей оставляет желать лучшего: на такую ужасную клавиатуру, какая применялась раньше на БК-0010.01, они не рассчитаны[27].
Для выдачи сигнала на магнитофон с выхода МС D13 (на БК-0010.01 схема этого узла несколько изменена) служит делитель на резисторах R29-R31, а для приёма сигнала с магнитофона - компаратор на транзисторах VT1, VT2. Дистанционное управление магнитофоном осуществляет реле К1 (неправильно обозначенное на схеме как К1.2) типа РЭС-15. Его нормально разомкнутые контакты К1.1 подключены непосредственно к разъёму МГ и ни с какими цепями внутри БК не соединяются. Таким образом, на контакты ДУ разъёма МГ можно подавать любое напряжение (по техническим условиям для данного реле оно не должно превышать 115 В), но. учитывая, что цепи, соединяющие реле с разъёмом МГ, проходят по печатной плате компьютера, не следует использовать реле К1 для коммутации цепей с напряжением свыше 12-24 В и током более нескольких десятков миллиампер.
Все цепи внутренней магистрали ЭВМ (и некоторые другие) нагружены на буферные резисторы, объединённые в резистивные матрицы (Е1-Е8).
Цепь запуска микро-ЭВМ выполнена на МС D6 типа К561ЛЕ5 и обеспечивает определённый порядок подачи сигналов на входы DCLO и ACLO ЦП после включения питания или срабатывания переключателя ручного перезапуска системы («ПУСК-СТОП», неправильно обозначенный на схеме как SA2, на БК-0010.01 этот переключатель отсутствует[28]).
Центральным элементом модуля памяти (показанного на второй части схемы) является контроллер ОЗУ и дисплея на МС К1801ВП1-037 (D19). Эта микросхема многофункциональная. Получая по магистрали микро-ЭВМ сигналы чтения, записи и адреса, она преобразует их в обращение к конкретному адресу матрицы ОЗУ, выполненной на МС ОЗУ динамического типа (К565РУ6) DS1-DS16 общей ёмкостью 32 Кб. Для промежуточного хранения данных используется регистр на МС D22, D23 типа К589ИР12, а для преобразования содержимого экранного ОЗУ в последовательный растровый код - регистр сдвига на МС D24, D25 типа К155ИР13. Контроллер формирует также сигналы регенерации динамического ОЗУ и полный телевизионный (ТВ) сигнал. Дальнейшая обработка ТВ-сигнала осуществляется МС D9.2, D10, D20, D21. Выходной ТВ-сигнал черно-белого изображения снимается с выхода оконечного видеоусилителя на транзисторах VT8, VT9, а цветного («R», «G», «В» и «Синхро») - с выхода повторителей на транзисторах VT4-VT7 (последние четыре транзистора, как и RGB-выход вообще, на старых БК-0010 отсутствуют). С выхода Е контроллера снимается сигнал, блокирующий работу ПЗУ в области адресов системных регистров (177600... 177777). Микросхемы ПЗУ DS17-DS19 являются отдельными элементами схемы, связанными с ЦП по системной магистрали асинхронно. На их подключении следует остановиться чуть подробнее.
Прежде всего, на указанной схеме приведены лишь МС типа К1801РЕ2 с номерами 017, 018 и 019, что соответствует схеме старой БК-0010 (монитор, Фокал и тест-ПЗУ). В БК-0010.01 вместо двух последних МС установлены три ПЗУ Бейсика (номера 106, 107, 108). Микросхема типа К1801РЕ2 имеет вход выборки СЕ. Схема БК-0010.01 устроена так, что при подключении блока МСТД микросхемы Бейсика, стоящие на плате, отключаются по этим входам и уже не отвечают на запросы ЦП, а установленные в блоке МСТД микросхемы К1801РЕ2-018 и -019 включаются в работу. Микросхема, адресное пространство которой приходится на область системных регистров (019 - тест-ПЗУ или 108 - Бейсик), дополнительно блокируется сигналом запрета с выхода Е контроллера D17, если ЦП обратился по адресу какого-либо системного регистра. Чтобы отключить любую из МС ПЗУ. достаточно на её вход СЕ (вывод 23) подать «логическую 1», при этом МС перестаёт отвечать на запросы ЦП и занимать адресное пространство, она как бы не существует. Таким способом можно почти неограниченно расширять память БК-0010, «размещая» новые страницы памяти (ОЗУ или ПЗУ) в адресном пространстве ПЗУ при условии отключения последних. Конечно, ни при каких обычных условиях недопустимо отключение ПЗУ монитора (DS17), иначе ЭВМ просто не будет работать (хотя ЦП будет продолжать функционировать), ибо это ПЗУ выполняет функции виртуального процессора.
Возможные доработки БК-0010
Среди пользователей БК-0010 в последнее время появилась мода на доработки схемы БК. Считается чуть ли не неприличным, если эта схема осталась неизменной и работает так, как это предусмотрено. Нельзя не признать, что многие из доработок весьма рациональны, но есть среди них и сомнительные. Рассмотрим кратко, какие доработки БК-0010 сегодня известны, в чем их преимущества и недостатки, а также насколько велик риск выхода компьютера из строя при их реализации.
Самой распространённой доработкой является исключение блока МСТД с установкой платы блока (или непосредственно микросхем ПЗУ Фокала и Бейсика) внутрь корпуса компьютера. Один из вариантов этой доработки описан в журнале «Информатика и образование» (там же, где приведена схема БК). Доработку надо признать весьма рациональной и при аккуратном исполнении безопасной.
Одновременно можно установить переключатель перезапуска компьютера, если он отсутствует. Его удобно вынести на левую стенку корпуса, как это показано в журнале «Персональный компьютер БК-0010 - БК 0011М», №1 за 1994 г., с. 71. Если использовать кнопку типа П2К, то её выводы точно устанавливаются в отверстия печатной платы, предназначенные для переключателя, остаётся только их распаять и прорезать в левой стенке корпуса окно для кнопки.
Следующим шагом может быть установка отдельных выключателей для каждой из микросхем ПЗУ. Для включения соответствующей МС достаточно соединить её вывод 23 с общим проводом компьютера («логический 0»), а для отключения подать на вывод 23 «1» (соединить вывод с шиной «+5В» через резистор 1...3 кОм), что и должны делать переключатели. Такая доработка может быть очень полезной. Например, если при исполнении какой-либо программы компьютер «завис» и не реагирует на нажатие клавиш, то после отключения всех МС ПЗУ (кроме ПЗУ монитора) и перезапуска содержимое ОЗУ сохранится (если, конечно, его не уничтожит программа, вызвавшая зависание) и имеющуюся в ОЗУ информацию можно использовать. Зависание системы перестаёт быть «фатальным»![29] И совершенно необходимо выборочное отключение ПЗУ при подключении к ЭВМ дополнительной памяти, дисковода и иных внешних устройств, работающих по адресам 120000...177577. Но эта доработка уже сложнее, так как требует вмешательства в схему компьютера (в частности, необходимо освободить от печатных проводников выводы 23 всех ПЗУ, сохранив при этом другие цепи нетронутыми).
Интересной и несложной доработкой является введение переключателя «ПДП» - прямого доступа к памяти. Конечно, настоящий режим ПДП мы получить не сможем: для этого необходимо как минимум специальное устройство - контроллер ПДП, но переход в режим ПДП позволяет обратимо (на время) прервать исполнение любой программы пользователя. Для перехода в этот режим необходимо подать «0» на выводы 2, 3 и 5 ЦП, т.е. с помощью специально установленного переключателя соединить эту точку схемы (указанные выводы МС D14 в стандартной схеме БК соединены вместе и через резистор R27 подключены к шине питания «+5В») с общим проводом. Отключать от этой точки что-либо, как это рекомендовалось в журнале «Информатика и образование». № 2 за 1990 г. (с. 46). не нужно - это только лишний риск вывести компьютер из строя, а останов ЦП будет достигнут и без этого. (В упомянутой статье помимо переключателя ПДП описана схема перевода БК-0010 в так называемый «турбо-режим» - удвоение тактовой частоты ЦП. Эта доработка призвана удвоить скорость исполнения программ на БК. При всей своей привлекательности эта доработка относится к числу не рекомендуемых. Во-первых, поскольку обмен в БК асинхронный, удвоения скорости не произойдёт, она увеличится в лучшем случае в 1.7 - 1.9 раза (а при работе с ОЗУ - всего в 1.2 - 1.6 раза). Кроме того, вообще не каждый БК-0010 будет работать при удвоенной тактовой частоте, это зависит от качества применённой в вашей ЭВМ микросхемы ЦП. И наконец, тактовая частота 6 МГц является предельной для ЦП данного типа и значительно увеличивает вероятность сбоев в работе ЭВМ, а также сокращает срок службы ЦП из-за его сильного перегрева[30].)
Не составляет труда ввести в компьютер и дополнительное внеприоритетное прерывание ACLO (по вектору 24). Для вызова этого прерывания достаточно подать короткий положительный импульс на вывод 13 МС D6.4. Импульс может быть сформирован, если зарядить конденсатор ёмкостью 0,033 мкФ до напряжения +5В, а затем разрядить его на эту точку схемы (с помощью специального переключателя или кнопки). В МСД при этом будет выдано сообщение «сбой питания». Перезадав вектор 24 и написав свою программу его обработки, можно решать самые различные задачи, например делать графическую копию экрана в любом режиме ЭВМ (надо только учитывать, что вектор 24 весьма «уязвим» - его переписывают команда EMT 14 и многие программы пользователя).
Следующая очень распространённая доработка - расширение памяти компьютера. Предложено множество схем такого расширения. все они сводятся в основном к отключению ПЗУ в области адресов 120000...177577 и подключению вместо них нового внешнего устройства - контроллера ОЗУ и матрицы-накопителя (попросту говоря, микросхем памяти). Контроллер необходим потому, что не существует микросхем ОЗУ, рассчитанных на непосредственное включение в системную магистраль БК-0010 (кроме сверхдефицитных К1809РУ1, имеющих к тому же ёмкость всего 2 Кб), - все они имеют организацию INTEL, а у нас в компьютере используется организация DEC. Есть, правда, МС ППЗУ типа К573РФЗ (с ультрафиолетовым стиранием) и КМ1801РР1 (с электрическим стиранием), рассчитанные на архитектуру DEC; это перепрограммируемые ПЗУ, по подключению идентичные МС К1801РЕ2. Но эти МС крайне дефицитны, для записи в них информации необходимо специальное оборудование (программатор), а срок хранения информации в них ограничен (в лучшем случае несколько тысяч часов), поэтому всерьёз говорить о их широком применении в БК не приходится.
Все схемотехнические решения дополнительного ОЗУ распадаются на две ветви - статическое и динамическое ОЗУ. Динамические МС ОЗУ (преимущественно серии К565) относительно дёшевы и обладают значительной ёмкостью (от 16 до 64 и больше килобит на корпус), но требуют периодической регенерации - восстановления информации, осуществляемого специальным устройством примерно 1000 раз в секунду. Это усложняет схемотехнику такого типа ОЗУ или требует применения специальных контроллеров (например К1801ВП1-037), а они дефицитны. Кроме того, информация в динамическом ОЗУ хранится, пока включено питание компьютера. Любой сбой питания или выключение ЭВМ приводит к необходимости загружать информацию в ОЗУ заново, а при его значительной ёмкости (порядка 128 Кб) такая загрузка с магнитофона будет длиться десятки минут. Ясно, что применение ОЗУ столь большой ёмкости оправдано в основном для промежуточного хранения и обработки информации (графической или текстовой). Для этого, естественно, требуются специализированные программы, на БК-0010 пока что отсутствующие (их разработка ведётся, но ассортимент вряд ли будет широк - маловато самих дополнительных ОЗУ. не всем они доступны). Ещё один «минус» динамических ОЗУ - значительное энергопотребление. Ток потребления, например, ОЗУ ёмкостью 128 Кб может превышать 0,5 А. На такую дополнительную нагрузку блок питания БК не рассчитан, и требуется его переделка или изготовление дополнительного блока питания. Конечно, можно довериться и «штатному» блоку питания БК (по принципу «авось пронесёт») - некоторые резервы имеются, но полагаться на «авось» как-то не очень хочется.
Вторая ветвь - статические ОЗУ. Они конструируются обычно на базе МС со сверхнизким энергопотреблением, чаще всего серии К537. Эти МС, не обладающие столь большими информационными ёмкостями и более дорогие, тем не менее имеют ряд важных преимуществ. Прежде всего, они не требуют регенерации, поэтому схема ОЗУ получается проще. Но самое главное - потребляемый ток этих МС так мал, что они не только не создают перегрузок блока питания БК, но даже позволяют снабдить такое ОЗУ «буферным питанием». т.е. блоком питания от батарей или аккумуляторов, позволяющим хранить информацию при отключении ЭВМ несколько месяцев или даже лет. Благодаря такому решению уже не приходится каждый раз загружать информацию в ОЗУ заново, однажды считанные туда программы (например отладчик, ассемблер, редактор текста) могут храниться неограниченно долго, при включении ЭВМ они сразу готовы к работе, а при необходимости их очень легко заменить на другие программы. Понятно, что применение статических ОЗУ с малым энергопотреблением и буферным питанием (их ещё называют энергонезависимыми ОЗУ - ЭОЗУ) гораздо предпочтительнее динамических[31].
Основным преимуществом в случае расположения рабочих адресов дополнительного ОЗУ в зоне адресов ПЗУ является то, что загруженные в такое ОЗУ программы находятся в адресном пространстве БК и (если они перемещаемые) могут работать непосредственно в дополнительном ОЗУ. Если же требуется переслать такую программу в ОЗУ пользователя, то это тоже очень легко сделать, воспользовавшись, например, директивами МСД. Но существует и другой путь. Можно подключить дополнительное ОЗУ к порту БК-0010 и организовать его побайтный обмен информацией с ОЗУ пользователя, получив таким образом внешний накопитель информации - квазидиск. Преимуществом такого решения является относительная простота ОЗУ, но много и недостатков. Это и необходимость использования специальных программ обмена (типа операционных систем), и невозможность свободного подключения к порту других внешних устройств (принтера, манипулятора «мышь»), и обязательность пересылки программ для работы в ОЗУ пользователя. Разумное сочетание обоих решений (например, программы обмена и некоторые инструментальные программы - в ЭОЗУ, в общем адресном пространстве, а хранение промежуточной информации - на квазидиске) может дать хорошие результаты. Однако сегодня всё упирается в отсутствие специальных программ, рассчитанных на работу с дополнительными ОЗУ, которые позволили бы в полной мере реализовать преимущества этой доработки БК-0010.
Появление в последнее время в свободной продаже нескольких типов принтеров привело к тому, что принтер всё чаще становится неотъемлемой частью комплекса на основе БК-0010, чему немало способствует наличие на БК неплохих текстовых и графических редакторов. Про программное обеспечение печати уже было рассказано в разделе «Концепция системных драйверов». Остаётся только указать, какой принтер наиболее предпочтителен. Сегодня это, несомненно, МС6313 с EPSON-совместимой системой команд. Ничего плохого нельзя сказать и про импортные модели принтеров (например, EPSON LX/FX-800), но где их взять рядовому пользователю БК? Да и цены кусаются. А распространившиеся было термоструйные принтеры МС6312 мало кому пришлись по нраву, так как печатающие головки-чернильницы на них одноразовые, довольно дорогие и дефицитные. Одна головка даёт возможность отпечатать не более 800-1000 страниц, после чего требует замены (придуманные «умельцами» способы перезаправки головок стабильных результатов не дают). Таким образом, даже более низкая стоимость МС6312 по сравнению с другими моделями не окупает хлопот и расходов при его эксплуатации. Дополнительными недостатками этого принтера являются невозможность печати нескольких экземпляров текста одновременно и очень мелкий шрифт[32].
Проблемой номер один на БК считается подключение дисковода, хотя реализована эта доработка пока очень немногими. Обычно для этого используется схема контроллера НГМД для компьютера БК-0011 (основой которого является микросхема К1801ВП1-128). Эта схема неплохо себя зарекомендовала, не требует для подключения почти никаких доработок компьютера и имеет поддержку в виде целого ряда программных средств (операционных систем). Конечно, имеющиеся программные средства поддержки дисковода пока далеки от совершенства. Сильно ограничивает возможности работы с дисководом тот факт, что программа обмена или ОС должна при работе с ним располагаться в ОЗУ пользователя, что делает невозможной работу со многими программами, требующими всего объёма памяти. Обойти эту трудность можно, разместив ОС в дополнительном ОЗУ любого типа, лучше всего, конечно, в ЭОЗУ. Однако следует отметить, что необходимость подключения к БК дисковода часто переоценивается. Разумеется, существует ряд задач, для решения которых дисковод просто необходим. Но для большинства задач вполне достаточно магнитофона, особенно с последними разработками, например разработанным автором МКМ-пакетом, который позволяет вести обмен с магнитофоном на скорости 4000 бод при высокой надёжности и поддерживает большинство программ пользователя и языков высокого уровня. При размещении такого драйвера в дополнительном ОЗУ удобство работы с магнитофоном возрастает столь заметно, что необходимость подключения дисковода становится неочевидной... на некоторое время.
«Сколько на свете людей, столько и мнений»: старая поговорка права всегда, и, в полном соответствии с ней, мнение редакции журнала в данном случае не совпадает с мнением автора. Конечно, дисковод пока ещё является скорее удобством, чем необходимостью. Но для любого компьютера удобство работы - один из определяющих факторов, к тому же уже сегодня существует немало многофайловых программ, при работе подгружающих в ОЗУ тот или иной программный модуль, и количество таких программ (в связи с растущей популярностью дисководов и удобством самого модульного, или, как ещё говорят, оверлейного, принципа программирования на ЭВМ с ограниченным объёмом памяти) будет увеличиваться с каждым годом. Далее, в настоящее время использование КНГМД от БК-0011, упомянутых автором статьи, почти полностью прекращено, так как прошивка микросхемы ПЗУ в этой модели КНГМД обладает рядом существенных недостатков. (В частности, перед загрузкой операционной системы с диска на БК-0010(.01) требуется вручную переписать содержимое некоторых ячеек ОЗУ). Сейчас к БК-0010(.01) подключают в основном КНГМД от новой модели БК-0011М («триста двадцать шестая» прошивка ПЗУ), соответственным образом доработанный:
- установка перемычек, блокирующих ПЗУ Бейсика при подключении блока КНГМД к разъёму системной магистрали БК-0010.01 (или ПЗУ Фокала и тестов в БК-0010);
- установка дополнительного энергонезависимого ОЗУ ёмкостью от 8 до 32 Кб (стандарт-16 Кб);
- прочие доработки, повышающие удобство общения с БК и расширяющие спектр его возможностей: кнопка «RESET», микросхема для временного подключения ПЗУ Бейсика взамен ДОЗУ («дисковый Бейсик») и т.д.
Кроме контроллеров от БК-0011М популярны и «самостоятельные» разработки, предназначенные специально для БК-0010(.01). Из них наиболее предпочтительными, пожалуй, можно считать контроллеры фирмы «АльтПро», описание которых дано в предыдущем выпуске журнала.
Вопросам подключения дисковода к БК-0010(.01) посвящён №1 за 1993 г., имеются публикации и в других выпусках (в частности, в №5 за 1994 г. дан ассемблерный листинг прошивки ПЗУ КНГМД и объяснены основные принципы его работы, а в №2 за 1995 г. рассказано о доработке КНГМД для работы с диском в вильнюсском Бейсике БК-0010.01).
Приобрести контроллеры дисковода для БК-0010(.01) (фирмы «АльтПро») и для БК-0011(М). а также дисководы, блоки питания для них, операционные системы и даже полный комплект «винчестера» для БК-0010(.01) и БК-0011(М) (сам жёсткий диск, совмещённый контроллер для него и дисководов, блок питания и программная поддержка) можно в редакции, здесь же вы можете получить справки и консультации. - Прим. ред.
Для БК-0010 в настоящее время разработан и ряд других дополнительных устройств, например серийно выпускаемый манипулятор «мышь» (УВК-01 «Марсианка») и несколько видов джойстиков. Если «мышь» имеет стандартную цоколёвку, которую нужно учитывать только авторам использующих её программ, то с джойстиками дело обстоит сложнее. Единого стандарта их подключения к БК не существует, так что риск несовпадения распайки у авторов программ и у пользователей довольно велик. Но проблема, как это часто бывает, преувеличена. Подавляющее большинство программ, использующих джойстик (как правило, это игры), дает возможность задания управления перед началом работы, поэтому способ подключения джойстика особого значения не имеет. Надо только найти общий вывод для всех его кнопок и подключить его к общему проводу разъёма порта УП БК (например, к контакту All), остальные же выводы подключаются к произвольным контактам порта ввода (например, к В24, А24, В23, В17, В20, А20, В22 и т.д.)[33].
Прочие дополнительные устройства применяются на БК относительно редко и являются более «экзотическими». Например, в Чехословакии (по старой географической терминологии) специально для БК выпускался плоттер (двухкоординатный графопостроитель) типа XY4140 (XY4150, XY4160) с соответствующим программным обеспечением, входящим в комплект поставки. Есть и программы, разработанные для него дополнительно. Например, автором этой статьи создана программа «СКР.4140», позволяющая выводить на плоттер любые изображения, подготовленные в графическом редакторе Р1.0М (доработанный БК-PAINT), в том числе и размечать с его помощью печатные платы. Автором также разработана система ввода в БК изображений с телекамеры - телевизионный сканер «ТВИНТ», имеющий очень несложную аппаратную поддержку (не считая, конечно, самой телекамеры типа «Электроника-841»!), так как большинство проблем решается программным путём. Разными авторами в разное время разрабатывались на базе БК-0010 многоголосые музыкальные синтезаторы, системы автоматизации, приставки к телефонам и т.п., но все эти устройства были достаточно сложны и не получили заметного распространения [34].
«Бичом» старых БК-0010 была клавиатура. Став более удобной на БК-0010.01, она проиграла в надёжности. Разработано несколько способов подключения к БК внешней клавиатуры (в том числе и от IBM) - на герконах, на магнитодиодных микросхемах и т.п., но все они достаточно сложны и дороги в реализации. Существует и целый ряд приёмов борьбы с дребезгом «штатной» клавиатуры, более или менее эффективных. Но с выпуском новой клавиатуры для БК (с многослойной плёночной контактурой и характерными «ступеньками» на клавишах управления курсором) эта проблема утрачивает остроту.
Подводя итоги всему сказанному, можно заметить, что БК-0010 благодаря своей относительно низкой стоимости, широкой распространённости, возможности расширения, удобной системе команд и развитому программному обеспечению ещё достаточно долго «не сойдёт со сцены» и будет продолжать радовать своих владельцев всё новыми возможностями, а перед программистами и схемотехниками ставить всё новые задачи.
приложения
Перечень общепринятых в литературе о БК сокращений
DIN - сигнал цикла ввод (чтение) активного устройства.
DOUT - сигнал цикла вывод (запись) активного устройства.
PC - программный счётчик, один из регистров общего назначения.
PS - регистр состояния процессора (в котором хранится слово состояния процессора).
PSW (ССП) - слово состояния процессора.
RPLY (СИП) - синхроимпульс (ответ) пассивного устройства.
SYNC (СИА) - синхроимпульс (синхронизация обмена) активного устройства.
SP - регистр хранения адреса вершины стека, один из регистров общего назначения.
WTBT - сигнал байтового обмена цикла вывод (запись) активного устройства.
АД - адрес-данные (шина, группа линий связи системной магистрали).
«АР2» («НР») - регистровая клавиша для ввода дополнительных кодов клавиатуры на БК - псевдографики и некоторых команд управления.
АУс - активное устройство (инициирующее обмен по системной магистрали).
АЦП - аналого-цифровой преобразователь (устройство, преобразующее сигналы тока или напряжения в цифровой код).
БК - бытовой компьютер (здесь имеется в виду компьютер БК-0010(.01) или БК-0011 (М) )[35].
ГТ - графическая точка.
ДАМП - распечатка содержимого памяти (обычно в коде ASCII) а также специальная программа для такой распечатки.
ДВК - диалоговый вычислительный комплекс, групповое название распространённых компьютеров конфигурации DEC.
ДУ - дистанционное управление (магнитофоном или иными устройствами).
«ЗАГЛ» - регистровая клавиша, устанавливающая режим ввода кодов заглавных букв.
КС - контрольная сумма (файла, блока информации, массива) - специальным образом вычисляемое значение, позволяющее идентифицировать блок информации (как правило, используется для проверки правильности ввода с клавиатуры или чтения с внешнего носителя).
КУВТ - комплекс учебной вычислительной техники[36].
«ЛАТ» - регистровая клавиша, устанавливающая режим ввода кодов латинских букв.
МДС - монитор-драйверная система, часть системного программного обеспечения БК-0010.
МЛ - магнитная лента.
МОП - одна из технологий производства микросхем (металл-окисел-полупроводник).
МПИ - разъем системной магистрали БК.
МС - микросхема.
МСД, МСТД - мониторная система тестов и диагностики, часть системного программного обеспечения БК-0010, а также блок (МСТД), подключаемый к разъёму системной магистрали.
НГМД - накопитель на гибких магнитных дисках.
НМД - накопитель на жёстком магнитном диске («винчестер»).
НМЛ - накопитель на магнитной ленте.
ОЗУ - оперативное запоминающее устройство.
ОС - операционная система.
ПДП - устройство (режим) прямого доступа к памяти.
ПЗУ - постоянное запоминающее устройство.
ПМ - пусковой монитор (часть системного программного обеспечения, а также один из диалоговых режимов БК-0010).
ПНЧ - преобразователь напряжения в частоту.
ПО - программное обеспечение.
ППЗУ - перепрограммируемое постоянное запоминающее устройство.
«ПР» - регистровая клавиша, устанавливающая режим ввода дополнительных кодов клавиатуры.
ПУс - пассивное устройство (устройство, подключённое к системной магистрали и работающее под управлением активного устройства).
РОН - регистр общего назначения центрального процессора.
РП - режим расширения памяти БК-0010.
«РУС» - регистровая клавиша, устанавливающая режим ввода кодов русских букв.
СИА (SYNC) - синхроимпульс (синхронизация обмена) активного устройства.
СиМ - системная магистраль.
СИП (RPLY) - синхроимпульс (ответ) пассивного устройства.
СЛ - служебная линия (системной магистрали).
СР - системный регистр.
ССД - слово состояния дисплея.
ССП (PSW) - слово состояния процессора.
«СТР» - регистровая клавиша, устанавливающая режим ввода кодов строчных букв.
«СУ» - регистровая клавиша, устанавливающая режим ввода кодов символов управления.
СУБД - система управления базами данных.
ТЛГ - телеграфный канал связи.
УсП - установочная (настроечная) последовательность.
ЦП - центральный процессор.
ЭВМ - электронно-вычислительная машина (компьютер).
Цоколёвка разъёмов БК-0010(.01)
Вывод разъёма |
Назначение |
|
---|---|---|
Разъём порта УП |
Разъём магистрали МПИ |
|
А1 |
СБРОС (INIT) |
ОСТ (рестарт) |
А2 |
- |
Общий |
А3 |
- |
Общий |
А4 |
- |
- |
A5 |
- |
ПРТ (IRQ2) |
А6 |
- |
- |
А7 |
Вывод 07 |
- |
А8 |
+5 В |
- |
А9 |
+5 В |
- |
А10 |
- |
- |
A11 |
Общий |
- |
А12 |
- |
+5 В |
А13 |
Вывод 01 |
- |
А14 |
- |
БЛОК (откл. ПЗУ)1) |
A15 |
- |
- |
А16 |
Вывод 00 |
- |
А17 |
- |
- |
А18 |
Общий |
- |
А19 |
Общий |
- |
А20 |
Ввод 05 |
- |
А21 |
- |
- |
А22 |
- |
- |
А23 |
Ввод 07 |
ВВОД (DIN) |
А24 |
Ввод 01 |
ППР2 (предост. прер.) |
А25 |
Вывод 14 |
АД 13 |
А26 |
Вывод 12 |
АД 11 |
А27 |
Вывод 10 |
АД 09 |
А28 |
Вывод 08 |
АД 05 |
А29 |
Ввод 13 |
ЕН (блок. СР) |
А30 |
Ввод 15 |
- |
А31 |
Ввод 09 |
АД 00 |
А32 |
Ввод 11 |
- |
В1 |
Вход ПРТ (IRQ2) |
- |
В2 |
Прием ТЛГ2) |
Общий |
В3 |
Передача ТЛГ2) |
Общий |
В4 |
- |
ППР1 (предост. прер.) |
В5 |
Вывод 04 |
ТПР (требован. прер.) |
В6 |
Вывод 06 |
- |
В7 |
Вывод 05 |
АД 15 |
В8 |
+5 В |
|
В9 |
+5 В |
- |
В10 |
Вывод 03 |
- |
В11 |
Общий |
БАЙТ (WTBT) |
В12 |
Вывод 02 |
+5 В |
В13 |
- |
- |
В14 |
- |
- |
В15 |
- |
- |
В16 |
- |
- |
В17 |
Ввод 03 |
- |
В18 |
Общий |
- |
В19 |
Общий |
СБРОС (INIT) |
В20 |
Ввод 04 |
СИП (RPLY) |
В21 |
- |
ВЫВОД (DOUT) |
В22 |
Ввод 06 |
СИА (SYNC) |
В23 |
Ввод 02 |
АД 14 |
В24 |
Ввод 00 |
АД 12 |
В25 |
Вывод 15 |
АД 10 |
В26 |
Вывод 13 |
АД 08 |
В27 |
Вывод 11 |
АД 06 |
В28 |
Вывод 09 |
АД 04 |
В29 |
Ввод 14 |
АД 02 |
В30 |
Ввод 12 |
АД 03 |
В31 |
Ввод 08 |
АД 01 |
В32 |
Ввод 10 |
АД 07 |
Примечания:
1) - данный контакт используется только на БК-0010.01 и обеспечивает блокировку ПЗУ Бейсика при подключении блока МСТД.
2) - на некоторых экземплярах БК для обмена информацией по линии ТЛГ необходимо установить перемычки на печатной плате.
Некоторые полезные сведения о подключении к БК-0010(.01) контроллера дисковода
Для нормальной работы с БК-0010.01 КНГМД необходимо доработать: контакты А29 и А14 разъёма МПИ нужно соединить с шиной «+5В» через резисторы 150-200 Ом. Этим достигается отключение встроенного ПЗУ Бейсика при подключении КНГМД. Если в БК-0010.01 установлен переключатель для выборочного подключения ПЗУ Бейсика, Фокала и тестов, то такая доработка не требуется, достаточно перед подсоединением КНГМД просто отключить все встроенные ПЗУ. Для БК-0010 отключение ПЗУ по двадцать третьему выводу микросхем является единственным.
В КНГМД имеется дополнительный вывод шины МПИ (контакты под разъем на боковой стороне платы). К этим контактам можно подключать дополнительные устройства (например, блок энергонезависимого ОЗУ). Но при этом следует помнить, что линия системной магистрали WTBT на дополнительные контакты не выведена (если сигнал WTBT необходим в подключаемом устройстве, его следует снимать непосредственно с вывода В11 разъёма МПИ КНГМД).
Полный список системных переменных монитора БК-0010
******************** ФАЙЛ "DMBK1-B" **************************
СОСТАВИЛ: Эльтерман П.Б. тел.:183-18-39
Блок переменных драйвера БК-0010 ( DMBK )
(распределение ячеек системной области)
Мнемоника |
Формат |
Адрес |
Длина |
Функциональное назначение ячейки |
---|---|---|---|---|
Системные признаки DMBK (SYSPR ) |
||||
PRCW: |
BYTE |
40 |
1 |
Признак цветного режима (формат 32) |
PRINF: |
BYTE |
41 |
1 |
Признак инверсии фона |
PRRP: |
BYTE |
42 |
1 |
Признак расширенной памяти |
PRRUS: |
BYTE |
43 |
1 |
Признак русского регистра |
PRPODC: |
BYTE |
44 |
1 |
Признак подчёркивания символа |
PRINWS: |
BYTE |
45 |
1 |
Признак инверсии символа |
PRISU: |
BYTE |
46 |
1 |
Признак индикации су |
PRBLR: |
BYTE |
47 |
1 |
Признак блокировки редактирования |
PRGRAF: |
BYTE |
50 |
1 |
Признак графического режима |
PRZAP: |
BYTE |
51 |
1 |
Признак режима записи |
PRSTIR: |
BYTE |
52 |
1 |
Признак режима стирания |
PRCWSS: |
BYTE |
53 |
1 |
Признак цветного режима в служебной строке(СС) |
PRPSSS: |
BYTE |
54 |
1 |
Признак подчёркивания символа в СС |
PRISSS: |
BYTE |
55 |
1 |
Признак инверсии символа в СС |
PRGAHK: |
BYTE |
56 |
1 |
Признак гашения курсора |
Переменные драйвера клавиатуры ( DLLAW ) |
||||
WPTM: |
WORD |
100 |
2 |
Вектор прерывания таймера (ПРТ, IRQ2) |
SSPTM: |
WORD |
102 |
2 |
Слово состояния процессора (PS) |
BUFKL: |
BYTE |
104 |
1 |
Буфер клавиатуры |
PRZKB: |
BYTE |
105 |
1 |
Признак записи кода в буфер |
BKPOWT: |
WORD |
106 |
2 |
Буфер константы повтора (t паузы) |
PRPOWT: |
BYTE |
110 |
1 |
Признак повтора кода |
SCTAB: |
BYTE |
111 |
1 |
Счётчик табуляции |
MTAB: |
BLKW |
112 |
8 |
Маска табуляции |
SCKLC: |
BYTE |
122 |
1 |
Счётчик ключа |
TAKLC: |
WORD |
124 |
2 |
Текущий адрес ключа |
AKLC: |
BLKW |
126 |
24 |
Адреса ключей (0,1,2...9) |
Переменные драйвера ТВ-монитора ( DTWMN ) |
||||
PRNRUL: |
BYTE |
152 |
1 |
Признак нарушения рулона |
PRZTCK: |
BYTE |
153 |
1 |
Признак записи точки |
MASPGT: |
BYTE |
154 |
1 |
Маска позиции графической точки (ГТ) |
NMPGT: |
BYTE |
155 |
1 |
Начальная маска позиции ГТ |
NOMSIM: |
WORD |
156 |
2 |
Номер символа на экране |
AS: |
WORD |
160 |
2 |
Адрес символа |
DSIMB: |
WORD |
162 |
2 |
Длина символа в байтах |
KSVP: |
WORD |
164 |
2 |
Количество символов на экране |
AGTVP: |
WORD |
166 |
2 |
Адрес (номер) ГТ на экране |
AGT: |
WORD |
170 |
2 |
Адрес ГТ |
DGW: |
WORD |
172 |
2 |
Длина графического вектора |
SCTVS: |
WORD |
174 |
2 |
Счётчик ТВ-строк |
BUFX: |
WORD |
176 |
2 |
Буфер координаты X |
BUFY: |
WORD |
200 |
2 |
Буфер координаты Y |
ANVP: |
WORD |
202 |
2 |
Адрес начала видеопамяти |
BAZVP: |
WORD |
204 |
2 |
База видеопамяти |
DVPB: |
WORD |
206 |
2 |
Длина видеопамяти в байтах |
DGPB: |
WORD |
210 |
2 |
Длина графической памяти в байтах |
FON: |
WORD |
212 |
2 |
Фон экрана |
MASCW: |
WORD |
214 |
2 |
Маска цвета |
FONSS: |
WORD |
216 |
2 |
Фон служебной строки (СС) |
MSWSS: |
WORD |
220 |
2 |
Маска цвета СС |
SCKOD: |
WORD |
222 |
2 |
Счётчик кодов |
SCUIND: |
WORD |
224 |
2 |
Счётчик установки индикаторов |
Переменные драйвера магнитофона ( DMAG ) |
||||
PROH: |
BYTE |
300 |
1 |
Признак ошибки |
PRFCT: |
WORD |
302 |
2 |
Признак фиктивного чтения |
INCADR: |
WORD |
304 |
2 |
Инкремент адреса массива |
ABP: |
WORD |
306 |
2 |
Адрес блока параметров |
BUFSP: |
WORD |
310 |
2 |
Буфер указателя стека |
BUFKS: |
WORD |
312 |
2 |
Буфер контрольной суммы |
GRDL0: |
WORD |
314 |
2 |
Граница длины 0 |
|
|
|
|
|
BPDMAG: |
----- |
--- |
-- |
Блок параметров DMAG: |
|
|
|
|
|
KOM: |
BYTE |
320 |
1 |
Команда |
OTWET: |
BYTE |
321 |
1 |
Ответ |
ADRMAS: |
WORD |
322 |
2 |
Адрес массива |
DLMAS: |
WORD |
324 |
2 |
Длина массива на запись |
IMMAS: |
BLKB |
326 |
20 |
Имя массива |
ADRTM: |
WORD |
346 |
2 |
Адрес текущего массива |
DLTMAS: |
WORD |
350 |
2 |
Длина текущего массива |
IMTMAS: |
BLKB |
352 |
20 |
Имя текущего массива |
Системные переменные DMBK |
||||
DLBIT0: |
WORD |
250 |
2 |
Длина 0 DMAG (в условных единицах) |
DLBIT1: |
WORD |
252 |
2 |
Длина 1 DMAG (в условных единицах) |
DLBIT: |
WORD |
254 |
2 |
Длина бита DTLG (в условных единицах) |
KPORT: |
WORD |
256 |
2 |
Копия порта |
APOPKL: |
WORD |
260 |
2 |
Адрес программы обработки прерывания от клавиатуры |
PRWK: |
WORD |
262 |
2 |
Признак кода ВК (ввод) |
BUFSTA: |
WORD |
264 |
2 |
Буфер стартового адреса |
BUFDL: |
WORD |
266 |
2 |
Буфер длины массива |
Автор выражает благодарность всем программистам и пользователям БК, с которыми он вёл переписку и лично встречался за последние 7 лет, и чьи ценные предложения, советы, вопросы и пожелания помогли ему в написании данного материала:
Б. Ф. Фролкину, С. А. Кумандину и Д Г. Гаеву (Москва),
А. В. Цаплеву и А. Г. Макееву (С.-Петербург),
В. А. Михайлову (г Алма-Ата),
С. К Никанорову (г. Керчь) и другим.