Книга Ю.А. Зальцмана, опубликованная в журнале «Персональный компьютер БК-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, а во-вторых, познакомить его с основами программирования на самом гибком и мощном из языков любой ЭВМ - языке ассемблера.

По возможности будут пояснены также некоторые общие вопросы информатики и устройства ЭВМ. Ограниченный объём публикации, к сожалению, не позволяет вдаваться в подробности. Приходится также учитывать различный уровень подготовки читателей: одним, например, вопросы схемотехники компьютера совершенно недоступны, другим скучно изложение основ информатики, с которыми они давно знакомы... Поэтому предполагается, что эти общие (или, наоборот, узкоспециальные) вопросы читатель может изучить самостоятельно, с помощью имеющейся обширной литературы, в зависимости от своего желания и возможностей. Автор старался сделать изложение материала как можно более живым и доступным пониманию, что иногда вынужденно приводило к некоторой нестрогости изложения, расплывчатости формулировок и прочим дефектам, избежать которых полностью, конечно, не удалось. Но при написании статьи и не ставилась целью подготовка профессиональных программистов, а излагаемый материал отнюдь не претендует на роль истины в последней инстанции.

Рекомендуемая литература:

  1. Руководства и пособия, входящие в комплект БК-0010 (разные предприятия, выпускающие БК, комплектуют его различным набором литературы и под несколько отличающимися названиями, но примерно одного содержания).
  2. Вигдорчик Г.В., Воробьёв А.Ю., Праченко В.Д. Основы программирования на ассемблере для СМ ЭВМ. М.: Финансы и статистика, 1983.
  3. Сингер М. Мини-ЭВМ PDP-11: программирование на языке ассемблера и организация машины. М.: Мир, 1984.
  4. Фролов Г.И., Гембицкий Р.А. Микропроцессоры. Вып. 7. Автоматизированные системы контроля объектов. М.: Высшая школа, 1984.
  5. Брусенцов Н.П. Микрокомпьютеры. М.: Наука, 1985.
  6. Фрэнк Т.С. PDP-11. Архитектура и программирование. М.: Радио и связь, 1986.
  7. Брябрин В.М. Программное обеспечение персональных ЭВМ. М.: Наука, 1988.
  8. Осетинский Л.Г., Осетинский М.Г., Писаревский А.Н. Фокал для микро- и мини-компьютеров. Л.: Машиностроение, 1988.
  9. Талов И.Л., Соловьев А.Н., Борисенков В.Д. Микро-ЭВМ. В 8 кн. Кн. 1. Семейство ЭВМ «Электроника-60». М.: Высшая школа, 1988.
  10. Фролов Г.И., Шахнов В.А., Смирнов Н.А. Микро-ЭВМ. В 8 кн. Кн. 8. Микро-ЭВМ в учебных заведениях. М.: Высшая школа, 1988.
  11. Лин В. PDP-11 и VAX-И. Архитектура ЭВМ и программирование на языке ассемблера. М.: Радио и связь. 1989.
  12. Напрасник М.В. Микропроцессоры и микро-ЭВМ. М.: Высшая школа, 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, чрезвычайно гибки и разнообразны. Это и есть основное преимущество «дековской» системы команд. Но обо всём - в своё время.

Контрольные вопросы и задания

  1. Что такое архитектура ЭВМ?
    • Это аппаратные средства ЭВМ, доступные программным путём.

  2. Какова важнейшая часть ЭВМ, её сокращённое наименование?
    • Центральный процессор (ЦП).

  3. Как сокращённо обозначаются оперативное и постоянное запоминающие устройства?
    • ОЗУ, ПЗУ.

  4. Какие внешние устройства ЭВМ БК-0010 вы знаете?
    • Клавиатура, накопитель на магнитной ленте, дисплей.

  5. Через какие регистры БК обращается к своим внешним устройствам, их сокращённое наименование?
    • Регистры внешних устройств, или системные регистры (СР).

  6. Переведите восьмеричное число 3747 в двоичное (можно пользоваться таблицей, приведённой выше).
    • Порядок перевода:

      3

      7

      4

      7

      011

      111

      100

      111

      Ответ: 11111100111.

  7. Какова конфигурация БК-0010? Как она называется по наименованию фирмы, разработавшей данное направление?
    • Общая шина адрес-данные (конфигурация DEC).

  8. Какие конкретные преимущества и недостатки конфигураций 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 есть все их виды:

Уточним, что по техническим причинам одна микросхема ПЗУ БК-0010 включает не более 20000 байт (или 8 Кб) информации, поэтому все ранее перечисленные «части» ПЗУ, кроме последней, имеют эту длину.

Теперь вам ясно, что интерпретатор языка Фокал занимает одну микросхему ПЗУ, а языка Бейсик - три. Ясно также, что Фокал не может работать одновременно с Бейсиком, а Бейсик - с МСД, так как они занимают одни и те же адреса.

Контрольные вопросы и задания

  1. Каков размер (в байтах) адресного пространства БК-0010?
    • 65536 байт

  2. Сколько бит в байте? В слове?
    • 8 и 16, соответственно.

  3. Номер какого байта, старшего или младшего, равен номеру слова?
    • Младшего.

  4. Старший байт слова имеет чётный номер?
    • Нет, чётный номер имеет младший байт.

  5. Как машина различает обращение к младшему байту или к слову, имеющим один и тот же адрес?
    • В зависимости от команды обращения по этому адресу.

  6. В каком направлении нумеруются биты в слове - слева направо или справа налево? Какой номер имеет младший бит?
    • Справа налево. Младший бит имеет номер 0.

  7. Чему равен килобайт? Его сокращённое обозначение?
    • 1024д байта; «К».

  8. Могут ли программа и данные располагаться в памяти произвольно? От чего это зависит?
    • Да, по желанию программиста.

  9. Нарисуйте распределение адресного пространства БК-0010, изображая отдельные зоны прямоугольниками и наращивая адреса сверху вниз. Подпишите их наименования. Укажите адреса начала и конца каждой зоны и её длину в Кб (при выполнении последнего задания можно воспользоваться текстом статьи). Сделайте такой же рисунок для режима РП. Выучите распределение адресного пространства наизусть - это очень важно.
    • Последнюю область, «Область СР», вы имели полное право не изображать, так как мы с ней ещё не знакомы. Но на рисунке приведено всё, «как положено», чтобы к этому вопросу больше не возвращаться.

  10. Какова информационная ёмкость одной микросхемы ПЗУ БК-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). Итак, директивы:

В процессе проверки все расхождения информации в массивах выдаются на экран в следующем виде:

 

(эталонный массив)

(контролируемый массив)

(1)

адрес

данные

адрес

данные

(2)

адрес

данные

адрес

данные

 

.......

......

.......

......

(N)

адрес

данные

адрес

данные

Если массивы полностью совпадают, на экран ничего не выдаётся. Например, попробуем переслать в память и сравнить с исходным содержимое одного и того же ПЗУ. 100000А20000Д1000П - мы выполнили пересылку по адресу 1000. Теперь намеренно «испортим» массив в ОЗУ уже известной нам директивой: 2000А10Д0Р - мы записали в массив 8 байт (т.е. 4 слова) нулей. Теперь введём: 100000А20000Д1000С. На экран будет выдано четыре строки указанного выше вида - специально «сделанная» нами ошибка в составе контролируемого массива выявлена!

Необходимо отметить, что как слово, так и байт выдаются в виде шести цифр. При выдаче байта, конечно, три старшие цифры всегда нули. При вводе байта с клавиатуры может быть набрано любое количество цифр, но запись производится только по модулю 256д (т.е. остатка от целочисленного деления на 256) шести цифр, набранных последними. При байтовых операциях параметр А может быть и нечётным, при этом он определяет старший байт слова с чётным адресом, меньшим на единицу.

Пара последних директив полностью аналогична двум предыдущим, только работают они не со словом, а с байтом, и увеличение или, соответственно, уменьшение текущего адреса при их исполнении происходит не на 2, а на 1. Для этих команд действительны и нечётные адреса.

Заметим, что при исполнении трёх, последних директив магнитофон с помощью ДУ автоматически включается, а по окончании чтения или записи (или при нажатии клавиши «СТОП») - отключается. Нужно сказать, что ДУ магнитофона при работе с БК - громадное удобство, оценить которое могут лишь те, кто с ним работал. К сожалению, немногие бытовые магнитофоны имеют вход ДУ, совместимый с БК-0010, поэтому большинство пользователей никогда не работали с ним и даже не знают, чего они лишены! (Многие пользователи, имея в своём магнитофоне дистанционное управление, и не подозревают об этом! Так, в некоторых модификациях магнитофона «Электроника-302» ДУ «встроено» в цепь внешнего микрофона и может быть использовано при работе БК, если включить в гнездо микрофонного входа штекер стандартного магнитофонного кабеля БК. - Прим. ред.)

Теперь, когда мы ознакомились с директивами МСД, ответим на вопрос, для чего может быть полезна система отладки. Ясно, что в МСД можно как просматривать память ЭВМ, так и изменять её содержимое, т.е. заносить в ячейки памяти информацию. Зная язык машинных кодов (или команд), в МСД можно писать программы в кодах, вводить массивы данных, записывать их в виде файлов на МЛ, запускать, отлаживать.

Хотя язык машинных кодов не так сложен, как принято обычно считать, программировать непосредственно в кодах неудобно, это требует большого напряжения и отличной памяти (имеется в виду, конечно, память программиста, а не БК). Поэтому разработан специальный вспомогательный язык ассемблера, он позволяет достичь тех же самых результатов гораздо меньшей ценой. Зато, имея уже готовую распечатку программы в кодах, можно относительно легко ввести её в память, пользуясь директивами МСД. Это позволяет публиковать тексты сравнительно небольших программ в печатных изданиях, избавляя от необходимости тиражировать их непосредственно на МЛ. Если вам требуется ввести такую программу, то, чтобы избежать ошибок, рекомендуется сделать это дважды по разным адресам, а затем провести сверку этих массивов - маловероятно, что в обоих случаях вы сделаете одинаковые ошибки. Попробуйте ввести в память и запустить простейшую программу в кодах, для чего выполните команды:

1000А
12700, 14, 104016, 12700, 101, 104016, 0,
1000G

На выводимые по директиве «запятая» числа не обращайте внимания и набирайте новые. После запуска эта программа выполняет сброс экрана и выводит символ «А». Как видите, программирование в кодах - не такая уж сложная штука! (Если перед входом в режим МСД из тест-системы выполнить тест 1 (ОЗУ, ПЗУ), то при последующем вводе программы в кодах после нажатия на клавишу «запятая» будет каждый раз выводиться адрес очередной ячейки памяти. - Прим. ред.)

Директивы МСД для работы с магнитофоном, как легко догадаться, позволяют не только загружать программы с МЛ, но и копировать их, если после загрузки записать на МЛ загруженный массив, причём безразлично, на каком языке написана эта программа, нужно лишь знать её адрес и длину. (Кроме некоторых программ с автозапуском или загружаемых в ОЗУ экрана. - Прим. ред.)

В заключение приведём некоторые полезные сведения, которыми можно воспользоваться при работе в МСД.

Диагностические сообщения МСД

В процессе работы в МСД система ведёт развёрнутый диалог с пользователем, выдавая различные указания и сообщая результаты исполнения директив. Обычно сообщения МСД выдаются в достаточно понятной форме и не требуют особых комментариев, особенно если вы хорошо знаете директивы. Но есть несколько специальных диагностических сообщений, которые весьма кратки и нуждаются в пояснении. С одним из них мы уже познакомились:

Ещё два сообщения МСД нам пока не встречались, и их следует рассмотреть более подробно.

Необходимо отметить, что описанные сообщения выдаются только в том случае, если выполняются программы пользователя, загруженные и запущенные в МСД (либо при выполнении директив самой МСД). Если же загрузка и запуск программ производятся из ПМ либо программа пользователя изменяет значение векторов прерывания 4 и 10. диагностические сообщения МСД, разумеется, выдаваться не будут.

Итак, мы вкратце познакомились с мониторной системой диагностики БК-0010, хотя нам и пришлось ради этого надолго прервать описание архитектуры ЭВМ. Но затраченное время в дальнейшем окупится - теперь вы сможете не просто знакомиться с материалом, предлагаемым вашему вниманию, но во многих случаях активно проверять его на своём БК, экспериментировать. Никогда не упускайте случая потренироваться в работе с МСД, а заодно и проверить автора - он ведь тоже может ошибаться!

Контрольные вопросы и задания

  1. Для чего служит МСД?
    • Для просмотра и изменения содержимого ОЗУ, записи и чтения файлов, написания и отладки программ в кодах.

  2. Как называют знак диалога МСД?
    • Знак денежной единицы, «колесо».

  3. В каком регистре (РУС, ЛАТ, СТР, ЗАГЛ) подаются буквенные директивы МСД? Какое исключение из этого правила?
    • В регистре РУС-ЗАГЛ, за исключением директивы G.

  4. Выполните следующее задание:
    • запишите на МЛ содержимое монитор-драйверной системы БК-0010 (вы помните адрес и длину этого массива?);
    • загрузите записанный файл в экранное ОЗУ (с адреса 40000);
    • вызовите из ячеек 264, 266, 312, 346, 350 его параметры и поясните, «что есть что».
    • Порядок действий (сообщения МСД частично опущены):

      • М3; АДРЕС=100000; ДЛИНА=20000; ИМЯ=МДС;
      • МЧ; АДРЕС=40000; ИМЯ=МДС;
      • 264А4Л; ответ: 040000 020000 - адрес загрузки и длина файла МДС;
      • 346А4Л; ответ: 100000 020000 - «истинные» адрес и длина файла МДС;
      • 312АИ; ответ: 017341 - контрольная сумма файла МДС (не совпадающая, как видите, с контрольной суммой в МСД, равной 177777).
  5. Как перейти из МСД в ПМ без стирания экранного ОЗУ?
    • 4АЩ100442 «СТОП».

  6. Предположим, вы загрузили и запустили из режима МСД некоторую программу, например, ассемблер-систему. При работе с ней (или при запуске написанной в ней программы) получено сообщение: 3B003474. О чём это сообщение говорит и почему вы так думаете?
    • Сообщение говорит только о том, что произошло зависание; число 003474 не несёт никакой информации, так как работала программа пользователя, а не директивы МСД.

  7. Постарайтесь выучить директивы МСД и порядок работы с ними наизусть - вам часто придётся ими пользоваться.

Системные регистры

Совершая путешествие по нашему «городу» БК-0010, мы отметили, что «район» ПЗУ тянется почти до конца адресного пространства - до адреса 177577. А что же дальше? С адреса 177600 и до конца «улицы» (по адрес 177777) располагается так называемая область системных регистров.

Системные регистры микро-ЭВМ служат для связи с внешними устройствами, а также для некоторых других целей. Каждый регистр занимает в адресном пространстве одно слово, следовательно, имеет 16д разрядов, с нулевого по пятнадцатый. Простой арифметический подсчёт показывает, что в области адресов 177600...177776 содержится 100 слов, а значит, там можно разместить 100 (64д) регистров. Но далеко не все адреса этой области использованы в нашем компьютере. Рассмотрим имеющиеся в БК-0010 системные регистры в порядке возрастания их адресов. При этом нужно учитывать, что и в имеющихся регистрах далеко не все разряды (биты) используются. Некоторые разряды хранят только определённую, строго фиксированную информацию, которую нельзя изменить, подобно данным, хранимым в ПЗУ. В других информацию меняет только сама ЭВМ, а программист может лишь читать её и использовать результаты (говорят, что такие разряды доступны по чтению). В некоторые разряды регистров можно, наоборот, только записать информацию, но прочитать то, что записано, нельзя (разряды, доступные по записи). Наконец, есть разряды, в которые можно как записывать информацию, так и читать, - они доступны по записи и чтению.

Чем системные регистры (например, доступные по записи и чтению) отличаются от ячеек ОЗУ? Отличие существенное - информацию в ячейках памяти может записывать, читать и использовать только ЦП, он же выводит её на экран по вашей команде. А разряды системных регистров могут быть связаны непосредственно с внешними устройствами - с клавиатурой, магнитофоном, телеграфной линией, - словом, с внешним миром; могут принимать оттуда сигналы или. наоборот, передавать. Есть, правда, регистры, казалось бы, ни с чем не связанные, например таймер (о котором речь пойдёт позже). Но это не более чем заблуждение; таймер тоже связан с внешним миром - через физическое время!

А что же отсутствующие регистры? Это просто резерв для расширения нашей микро-ЭВМ в будущем, так сказать, пустые пока «участки под застройку» (в машинах серии ДВК, а также в БК-0010Ш, работающих в составе комплекса учебной вычислительной техники КУВТ-86, часть из них уже используется).

Итак, приступим к прогулке по последнему «переулку» адресного пространства. Вы готовы? Тогда - в путь!

Напомним, что каждый регистр имеет 16 разрядов, нумеруемых справа налево, причём нумерация начинается с нуля. Номера разрядов - десятичные. По ходу их перечисления будем также указывать, для чего можно использовать тот или иной разряд при программировании (как их использует ЭВМ - другой вопрос).

  1. Регистр состояния клавиатуры (адрес 177660). Используются только два разряда:
    • Разряд 06 - маска прерываний от клавиатуры. Если бит равен 0 - прерывание разрешено. 1 - запрещено. Доступен по записи и чтению. Можно использовать, чтобы программно «отключить» клавиатуру ЭВМ.
    • Разряд 07 - флаг состояния клавиатуры. Устанавливается в 1 при поступлении новых данных в регистр данных клавиатуры (см. ниже), например, при нажатии очередной клавиши, и сбрасывается в 0 при чтении. Доступен только по чтению. Если запретить автоматические чтение из регистра данных, этот разряд может использоваться, чтобы определить, была ли нажата клавиша за истекший промежуток времени. В обычных условиях, когда данные читаются сразу после нажатия клавиши, состояние разряда всегда 0, так как системные драйверы БК реагируют на нажатие клавиши быстрее, чем пользовательская программа.
  2. Регистр данных клавиатуры (адрес 177662). Используются семь младших разрядов:
    • Разряды 00...06 - буфер кода нажатой клавиши. При нажатии клавиши в него заносится 7-разрядный код, на который не влияют клавиши «НР» («АР2» для БК-0010.01) и «РУС». Полные коды клавиатуры получаются в БК-0010 программным путём. Разряды доступны только по чтению и широко используются для чтения кода клавиатуры независимо от регистра РУС-ЛАТ, для ввода команд без прерывания работы программы (в том числе и на языках высокого уровня) и т.п.
  3. Регистр смещения (адрес 177664). Используются 9 разрядов:
    • Разряды 00...07 отображают адрес начала экранного ОЗУ, которое организовано по типу рулона, причём их значение указывает количество телевизионных строк дисплея (каждая строка - 100 (64д) байт экранного ОЗУ). В исходном состоянии, когда началу экрана соответствует адрес 40000, содержимое этих разрядов равно 330. По мере сдвига экрана содержимое меняется причём, так как одна строка текста в БК-0010 содержит 12 (10д) телевизионных строк, содержимое регистра также меняется каждый раз на 12. Вверху экрана, как известно, имеется служебная строка, занимающая в экранном ОЗУ 2000 байт (каждая строка обычного текста - 1200 байт). Эти тонкости приводят к тому, что число сдвигов, необходимое, чтобы начало экрана получило снова адрес 40000, не кратно числу строк текста (24д) и содержимое младших разрядов данного регистра практически всё время разное. Разряды доступны по записи и чтению и могут использоваться для плавного сдвига изображения на экране по вертикали. Служебная строка при этом тоже смещается, поэтому данный способ требует специальных приёмов получения изображения.

    Нужно отметить, что в режиме «РП» экран организован совсем иначе, чем описано выше, служебная строка (начало экрана) всегда имеет адрес 70000, рулонное смещение не используется, а регистр всегда содержит константу 230.

    • Разряд 09 - задание режима расширенной памяти, что соответствует нулю в данном бите. Единица - обычный режим (в «Руководстве системного программиста» ошибочно указано наоборот). Разряд доступен по записи и чтению, может использоваться для обнаружения режима «РП», но включить расширенную память, просто записав туда 0, нельзя (вернее, этого недостаточно)
  4. Регистры системного таймера (адреса 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 г. - Прим. ред.)

  5. Регистр порта ввода-вывода (адрес 177714). Используются все 16 разрядов, доступные по записи и чтению. Но этот регистр особенный, он как бы составлен из двух отдельных регистров для ввода и вывода. Данные записываемые по адресу 177714, передаются в регистр вывода, а читаются из регистра ввода. Прочитать данные, записанные в регистр вывода, невозможно, поэтому, если необходимо сохранить к ним доступ, нужно записать их не только в порт, но и в специально зарезервированную для этого ячейку памяти (адрес 256). Необходимо заметить, что порт вывода инвертирует подаваемый на него код, т.е. при записи в него нулей на выходе будут единицы и наоборот. То же самое можно сказать и о порте ввода: при подаче на него от внешних устройств логических единиц программно читаются нули при подаче нулей - единицы. При «привязке» к порту внешних устройств нужно это учитывать.
  6. Регистр системных внешних устройств (адрес 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.

Контрольные вопросы и задания

  1. Как известными вам способами проследить за изменениями в регистре 177662 при нажатии разных клавиш? Проверьте это экспериментально.
    • Дайте в МСД директиву 177662АЦ, затем нажимайте различные клавиши и наблюдайте на экране их коды.

  2. Записывая в регистр 177664 различные числа, проследите за изменениями положения изображения на экране. Желательно предварительно заполнить экран каким-нибудь текстом, чтобы иметь ориентиры помимо служебной строки.
  3. Придумайте и осуществите на своём БК проверку работоспособности системного таймера.
    • Дайте в МСД директивы: 177706А0,,160 - при этом будет циклически читаться регистр счётчика таймера. Отметьте по секундомеру время между двумя моментами перехода таймера через ноль, этот интервал должен быть с точностью до нескольких секунд равен 3 мин, в противном случае таймер на вашем БК неисправен. Приостановить вывод информации на экран в МСД можно командой СУ/@, повторный пуск - любая клавиша или ещё раз СУ/@. Обратите внимание на то, что при прерывании вывода на экран таймер продолжает работать, после возобновления вывода его показания уже другие.

  4. Можно ли прочитать информацию, занесённую в МСД по адресу 177714? Придумайте, как всё-таки это сделать. Всё необходимое имеется в комплекте, прилагаемом к БК-0010.
    • Программно прочитать регистр вывода 177714 нельзя, но можно подать информацию с него на регистр ввода, для чего служит входящий в комплект ЭВМ «блок нагрузок». Подключив этот блок к порту ввода-вывода, можно читать из регистра 177714 информацию, записываемую по тому же адресу.

  5. Как проверить, не разбирая компьютер, подключён ли на вашей ЭВМ разряд 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)

Всё верно, ведь результат округлённый! Причём заметим, что округление происходит из-за отбрасывания младшего разряда. Но что. если нам нельзя округлять число? Ничего страшного, ведь у нас снова произошёл перенос за пределы слова и «флаг» переноса отметит, что результат нуждается в коррекции.

Итак, мы в общих чертах разобрались, как ЭВМ выполняет арифметические действия. Нетрудно теперь придумать, как можно умножить или разделить на число, не кратное двум (вспомните, что умножение - это не что иное, как многократное сложение, деление же - многократное вычитание, а также вспомните, как умножают «в столбик» и делят «уголком»). Не будем разбирать соответствующие примеры, так как это заняло бы слишком много места, а пойдём дальше в изучении архитектуры нашей ЭВМ.

Контрольные вопросы и задания:

  1. Как умножить двоичное число на 128?
    • Нужно выполнить 7 арифметических сдвигов влево.

  2. Придумайте правило, которое позволило бы умножать двоичные числа на 3, и сформулируйте его словами.
    • Чтобы умножить двоичное число на 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. Все его разряды доступны по записи и чтению, хотя запись в некоторые из них возможна только с помощью специальных команд или приёмов, а чтение может происходить вовсе не обязательно в том виде, к которому мы привыкли: ЦП сам, с помощью специальных команд читает эти разряды и использует их.

Что же это за разряды? Вначале идут четыре «флага», или разряда условий. Они принимают то или иное значение в зависимости от результата исполнения процессором очередной команды. Исходное состояние этих разрядов, если указанные ниже условия не выполняются, - нули:

Используя содержимое этих разрядов, можно организовать так называемое ветвление программы, т.е. переходы по условию, или что то же самое, по результатам исполнения команды. Помимо того, что эти разряды устанавливаются в 0 или 1 автоматически по результатам исполнения каждой команды, существуют и специальные операторы для их «ручной» установки.

Следующий разряд - 04, или T-разряд. Он имеет особое назначение. Если содержимое T-разряда равно 1, то программа приостанавливается каждый раз после выполнения очередной команды (происходит так называемое прерывание по T-разряду). Этот режим процессора широко используется в специальных программах-отладчиках, позволяющих исполнять программы по одной команде с целью их отладки (выполнять трассировку). Просто так записать в T-разряд единицу нельзя, для этого существует специальный приём, с которым мы познакомимся позже.

И наконец, последние три разряда (05, 06, 07) - это так называемые разряды приоритета процессора. Что это такое? В процессе работы ЭВМ ЦП постоянно обрабатывает информацию, или, проще говоря, решает задачи. Но ЭВМ работает и с внешними устройствами, например с клавиатурой. Можно организовать работу с клавиатурой двояко. По первому способу ЦП после выполнения определённого числа команд прерывает решение задачи и обращается к клавиатуре, опрашивая её. Если ни одна клавиша не нажата, вычисления будут продолжены, затем снова следует опрос клавиатуры и т.д. Этот способ довольно нерационален - независимо от того, нажата ли клавиша. ЦП вынужден всё время «отвлекаться» и тратить время на опрос клавиатуры. А если учесть, что на нажатие клавиши ЭВМ должна реагировать мгновенно (чтобы не создавать неудобств в работе), то понятно, что все клавиши должны опрашиваться никак не реже, чем 100 или даже 1000 раз в секунду.

Второй способ куда лучше - пусть ЦП решает задачу, а если будет нажата клавиша, то он получит так называемый запрос на прерывание. Это специальный сигнал, сообщающий процессору, что с какого-то из внешних устройств поступила порция информации и оно ждёт, чтобы ЦП отреагировал. ЦП прервёт решение задачи, обработает прерывание и продолжит вычисления.

Но постойте! Есть ведь такие задачи, когда ЦП никак не может «отвлечься», например работа с магнитофоном. Лента ведь не будет стоять и ждать, пока ЦП обработает прерывание... Вот для этого и служит приоритет. Если приоритет, установленный на данный момент для ЦП (как говорят, приоритет текущей задачи), ниже, чем установленный приоритет запроса на прерывание, то ЦП прервёт работу и обработает прерывание. Если же приоритет текущей задачи равен или выше приоритета запроса, то ЦП сначала закончит вычисления, а уже потом обработает прерывание. Сразу поясним, что приоритет запросов на прерывание от внешних устройств (у БК-0010 их, по сути, может быть всего два - клавиатура и внешний таймер) установлен для данной конфигурации ЭВМ раз и навсегда А вот приоритет ЦП может меняться и определяется содержимым указанных трёх разрядов PS.

Таблица 2

Источник прерывания

Приоритет

Адрес вектора

Использование источника

Зависание

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 векторы прерывания. Вектор прерывания обычно получает «имя» от того адреса, по которому записано его первое слово. Эти адреса строго фиксированы для данного типа процессора и поэтому точно определяют вектор.

(Другое название этого прерывания - «Прерывание пользователя», так как фактически оно может быть вызвано любым пользовательским внешним устройством, подающим сигнал на контакт В1 разъёма порта УВВ (справа) или А5 разъёма системной шины (слева). Таким образом, при описанном выше использовании прерывания таймер является примером внешнего устройства. Простейший способ вызова прерывания по вектору 100 - подача на контакт В1 порта УВВ сигнала «земля» (или «общий») с контактов А11, В11. А18, В18, А19 или В19 (так как порт УВВ работает с инверсными сигналами). Наиболее часто данное прерывание используется для печати копии экрана на принтер при нажатии замыкающей указанные контакты кнопки, как. например, в программе GRAFIX фирмы ALTEC или резидентном драйвере SCREW ANIRAM-MIRIADA. - Прим. ред.)

Остальные ячейки в области векторов прерывания БК-0010 используются для других целей. Но нам вполне достаточно и имеющихся векторов. Позже мы рассмотрим подробнее, что и как можно сделать с их помощью, а сейчас перейдём ко второй теме нашего разговора - к языку ассемблера.

Контрольные вопросы и задания

  1. Сколько регистров имеется в составе ЦП? Каковы их названия?
    • Восемь регистров общего назначения: R0, R1, R2, R3, R4, R5, R6 (или SP) и R7 (или PC), а также специальный регистр PS.

  2. Какие «флаги» состояния вы знаете, их буквенные обозначения и краткие наименования?
    • С - перенос, V - переполнение, Z - ноль, N - отрицательность.

  3. Если в результате выполнения какой-либо команды произошло переполнение и число стало отрицательным, то какое значение примут разряды условий?
    • С=0; V=l; Z=0; N=1.

  4. Что обеспечивает T-разряд ССП при его установке в единицу? Зачем это нужно?
    • Прерывание программы после выполнения каждой команды, это нужно для обеспечения отладочного режима (трассировки).

  5. Что хранится в первом слове вектора прерывания? А во втором?
    • Адрес программы обработки прерывания. ССП для данного прерывания.

  6. Чем отличаются командные прерывания от прерываний, вызываемых внешними устройствами?
    • Командное прерывание вызывается особой командой программы, понятие приоритета на него не распространяется.

  7. Учитывая, что разряды приоритета в ССП имеют номера 05, 06 и 07, напишите, какое машинное слово надо записать в регистр PS, чтобы получить приоритет 0, приоритет 4, максимальный приоритет. Считайте при этом, что остальные разряды PS равны нулю. Для облегчения задачи «нарисуйте» и пронумеруйте разряды PS, а получившееся потом число переведите в восьмеричное.
    • Приоритет 0 - 000000, приоритет 4 - 000200, максимальный приоритет (7) - 000340.

  8. Какой приоритет имеет клавиша «СТОП» БК-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 - 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». Остальные директивы монитора ещё долго вам не понадобятся (а если вы не освоите ассемблер всерьёз, то вообще никогда).

Вот и всё. Как вы считаете, всё это настолько сложнее Фокала или Бейсика, чтобы стоило об этом говорить? А куда как часто автору приходилось встречать людей, которые заявляли, что на ассемблере очень трудно работать, что он ужасно сложен и что освоить его может разве лишь гений... Скромность, конечно, хороша, но не тогда, когда она мешает прогрессу.

Контрольные вопросы и задания

  1. Каково основное отличие языка ассемблера от языков Фокал и Бейсик БК-0010?
    • Фокал и Бейсик - языки интерпретирующего типа, а ассемблер - компилирующего.

  2. Какая ассемблер-система самая лучшая?
    • МИКРО.10К-РП или МИКРО.10-01К. Это. конечно, шутка. Лучшая ассемблер-система - это та, которая вам нравится, или даже просто та, которая у вас есть. Некоторые же утверждают, что лучшая программа - это та, которая ещё не написана.

  3. Из скольких символов может состоять метка?
    • Не более чем из трёх в МИКРО.10К и из шести - в некоторых других системах.

  4. Локальная метка может начинаться с буквы?
    • Нет, только с цифры.

  5. Какие операторы ассемблера вы уже знаете?
    • CLR, CLRB, MOV, MOVB, END.

  6. Обязательно ли комментарии должны начинаться с символа «;»?
    • В МИКРО.10К - не обязательно, если они следуют за оператором или операндами.

  7. Наберите в ассемблер-системе программу, приведённую в качестве примера в этом разделе (комментарии можно не переписывать), и попробуйте её оттранслировать и скомпоновать (запускать не надо). Что обращает на себя внимание после компоновки?
    • Метки M1, М2, М3, М4, PRO после компоновки остались инверсными, так как они не определены - отсутствуют в исходном тексте.

  8. Является ли программирование на ассемблере сложной процедурой?
    • Автор утверждает, что нет, и в ваших интересах не пытаться его опровергнуть.

Способы адресации

Ранее мы установили, что в команде ассемблера оператор указывает, что надо сделать, а операнд - с чем это надо сделать. Как уже было сказано, поле операндов не обязательно (оно имеется не во всех командах). Но если их нет, это означает только то, что данный оператор в операндах не нуждается - они подразумеваются по умолчанию или производится обращение не к памяти ЭВМ, а к другим её устройствам.

Способы (или методы) адресации - это не что иное, как указание на ячейки памяти, с которыми должен манипулировать оператор. Таких методов в системе команд процессора БК вполне достаточно, чтобы обеспечить высокую гибкость в построении программы Многие способы адресации, кроме указания на адреса памяти, ещё и модифицируют их, т.е., по сути, выполняют функции дополнительных команд, а следовательно, значительно экономят память и повышают скорость исполнения программы. В большинстве случаев способы адресации одинаковы для всех команд, содержащих операнды (некоторые ограничения, относящиеся к отдельным операторам, будут оговорены особо).

Прежде чем перейти к изложению конкретных способов адресации, кратко напомним, какие в нашей ЭВМ вообще возможны операнды. Прежде всего, это ячейки памяти, лежащие в пределах адресного пространства ЭВМ (с адресами 0 - 177777). Адрес можно указывать с точностью до слова или до байта, но это уже скорее относится к формату записи оператора, а не операндов. Далее, это регистры общего назначения процессора (РОН): R0, R1, R2, R3, R4, R5, R6 и R7 (на языке ассемблера регистр R6 принято именовать SP, а R7 - PC). Напомним, что SP - это указатель вершины стека, а PC - счётчик команд (указатель адреса следующей команды). Подробно эти моменты рассматривались в разделах, посвящённых архитектуре ЭВМ, сейчас же важно, что все регистры, в принципе, также представляют собой ячейки памяти.

Теперь можно перейти к описанию собственно способов адресации. Приводя примеры, будем стараться использовать только те два оператора (CLR и MOV), которые нам уже знакомы. Регистр общего назначения в общем виде будем обозначать RN (это может быть любой из РОН, однако использование в качестве операндов SP и PC может иметь некоторые особенности).

  1. Регистровая адресация (иногда её называют ещё регистровой прямой, в отличие от косвенной). Операндом является один из РОН. Пример:
            MOV     R2,R4                ;Переслать (скопировать)
                                         ; содержимое R2 в R4
            CLR     R2                   ; Очистить (обнулить) R2
            MOV     R4,SP                ; Переслать содержимое R4 в SP

    Это самый простой способ адресации и, очевидно, в дальнейших пояснениях он не нуждается.

  2. Регистровая косвенная адресация. В РОН содержится не само число, с которым нужно работать, а его адрес, т.е. номер ячейки памяти, в которой оно находится. Обозначение: @RN или (RN). Значок «@» - это так называемое «коммерческое ЭТ», название длинное и труднопроизносимое, поэтому в программистском жаргоне его заменяет не слишком благозвучное, но зато удобное наименование «собачка». Обозначения @RN и (RN) абсолютно равнозначны, но лучше всё же писать @RN, чтобы не путать его с адресацией с инкрементом или декрементом, о которых речь пойдёт ниже. Примеры:
    1. пусть в регистре R2 записано число 1000, тогда команда CLR @R2 обнулит ячейку 1000 (ту, адрес которой указан в R2);
    2. пусть в R2 записано число 1000, а по адресу 1000 - число 1254. Тогда MOV @R2,R4 запишет в R4 число 1254, ведь именно его адрес указан в R2.
  3. Автоинкрементная косвенная адресация. Помимо основного действия (косвенного обращения к ячейке памяти) производится изменение (модификация) адреса этого обращения (ячейки). Слово «инкремент» означает увеличение чего-либо, в данном случае адреса ячейки памяти, к которой мы обращаемся (содержимого регистра, служащего указателем адреса). А «авто» - это указание на то, что данное увеличение происходит само, не требуя отдельной команды. Обозначение: (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, т.е. управление будет передано уже на следующую после константы команду.

  4. Автоинкрементная двойная косвенная адресация. Этот способ не слишком отличается от предыдущего, только в регистре-указателе при этом содержится не адрес числа, с которым должен работать оператор, а адрес адреса. Обозначение: @(RN)+.

    Пример: пусть в ячейке 1000 записано число 1254, а по адресу 1254 - число 3333. Тогда, если в R2 хранится число 1000, то команда MOV @(R2)+,R4 перешлёт в R4 число 3333, а содержимое регистра R2 станет равным 1002.

    Данный способ применяется относительно редко, но тем не менее он очень ценен, так как позволяет работать, например, с таблицами адресов. Пусть каждый элемент таблицы (а) является адресом одного из элементов таблицы (б). Данная адресация позволяет, «двигаясь» по элементам первой таблицы, проверять элементы второй, порядок следования которых может быть произвольным (не совпадать с порядком следования их адресов).

  5. Автодекрементная косвенная адресация. Если сообщить, что «декремент» означает уменьшение чего либо, то этот способ будет почти понятен, не правда ли? У него только два отличия от автоинкрементной косвенной адресации: во-первых, адрес в регистре-указателе не увеличивается, а уменьшается; во-вторых, это происходит не после выполнения команды, а до неё, на что указывает запись -(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 г. - Прим. ред.)

★ ★ ★

  1. Автодекрементная двойная косвенная адресация. Обозначение: @-(RN). Если сделать уже известные нам поправки, что декремент - это уменьшение, и выполняется оно ДО обращения к ячейке, на которую указывает операнд, то из автоинкрементной двойной косвенной адресации легко получается автодекрементная. Далее, не детализируя, приведём пример. Пусть в регистре R2 записано число 1002, по адресу 1000 - число 1254, а по адресу 1254 - 3333. Тогда команда MOV @-(R2),R4 запишет в R4 число 3333. Данный способ может быть применён для тех же целей, что и автоинкрементная двойная косвенная адресация, но даёт возможность, скажем, просматривать таблицу адресов с конца.

    Отметим, что такого, казалось бы, логичного способа как «двойная косвенная адресация», записать которую можно было бы, например, как @(RN), не существует. А жаль - он бы тоже мог пригодиться... Но ещё не всё потеряно, мы можем «обмануть» ассемблер и всё-таки задать адресацию таким образом! Как именно - увидим позже.

    Кроме того, заметим, что все ранее рассмотренные способы адресации «вписывались» в формат самой команды, и, таким образом, она при переводе в машинный код занимала одно слово. Способы, описываемые ниже, таким преимуществом не обладают. Они требуют задания некоторых дополнительных параметров, в формат одного слова никак не укладывающихся. Такие команды занимают два, а то и три машинных слова, однако потеря объёма памяти компенсируется ещё большей гибкостью и универсальностью средств программирования на ассемблере. Но прежде чем перейти к описанию этих способов адресации, введём ещё два общих обозначения: X - восьмеричное число, MET - произвольное имя метки.

  2. Индексная адресация. Это разновидность косвенной адресации через регистр. Обозначение: 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π с некоторым шагом, таким способом можно реализовать вычисление координат точек окружности (или дуги) с заданным радиусом и, соответственно, ассемблерную подпрограмму для её вычерчивания. - Прим. ред.)

  3. Индексная косвенная адресация. Обозначение: @X(RN) или @MET(RN). По смыслу она близка к предыдущей, но в ячейке, определяемой суммой индекса и содержимого регистра, теперь находится не само число-операнд, а его адрес. С таким явлением - двойной косвенностью - мы уже встречались, поэтому приводить примеры не будем (читатель может придумать их сам), а лучше выполним данное ранее обещание - покажем, как реализовать через регистр двойную косвенную адресацию, которая в наборе команд ЭВМ «официально» не существует. Как уже говорилось, логично было бы представить запись такой адресации в виде @(RN), мы же вместо этого запишем @0(RN) Цель достигнута - мы обратились к ячейке с двойной косвенностью и без модификации содержимого регистра-указателя. Правда, за этот «обман» придётся платить - команда занимает целых два машинных слова, хотя содержимое второго из них всего лишь нуль.

    Индексная косвенная адресация применяется редко, но иногда без неё почти невозможно обойтись. Например, когда нужно не просто перекодировать последовательность чисел, а сравнить её с другой последовательностью (возможно, тоже перекодированной!). С такой необходимостью вы непременно столкнётесь, если захотите написать программу сортировки по алфавиту русских слов - ведь символы русского алфавита в БК не упорядочены по возрастанию кодов.

  4. Непосредственная адресация. Этот способ позволяет в качестве одного из операндов использовать число - константу, которая записывается как #Х или #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", "А" и "="

    Если при такой адресации в апострофах записан один символ, то его код заносится в младший байт регистра (или ячейки памяти), если два - код первого заносится в младший байт, а второго - в старший. (Коды, не имеющие символьного представления, например перемещения курсора, управление выводом на дисплей и т.д., приходится вводить «по старинке» - с помощью #<КОД>. - Прим. ред.)

  5. Абсолютная адресация. Этот способ прост и прямолинеен: прямо указывается абсолютный адрес ячейки памяти, к которой мы обращаемся. Обозначение: @#Х. Восьмеричное число X помещается при трансляции во второе или третье слово команды. Пример:
            CLR     @#156                ; Очистка слова по адресу 156
            CLRB    @#45                 ; Очистка байта по адресу 45
            MOV     @#100112,R2          ; Запись содержимого слова 
                                         ; по адресу 100112 в R2
            MOV     #137,@#1000          ; Запись числа 137 по адресу 1000

    Данный способ достаточно широко применяется для обращения к ячейкам вне самой программы, например по адресам системной области или ПЗУ. Обращаться же к ячейкам, находящимся в ОЗУ пользователя (или тем более в самой программе), удобнее с помощью относительной адресации.

  6. Относительная адресация. Этот способ является, пожалуй, самым распространённым при задании адресов переходов и вызове подпрограмм (что мы рассмотрим в своё время), а также при обращении к ячейкам памяти (переменным), расположенным в ОЗУ пользователя, особенно в пределах самой программы. Обозначение: MET. При таком обращении ассемблер во время трансляции вычисляет смещение (разность между адресами текущей команды и метки MET) и записывает это смещение вторым или третьим словом команды. При исполнении программы адрес операнда определяется как сумма текущего содержимого счётчика команд PC и смещения. Последнее вычисляется в дополнительном коде с учётом знака, поэтому такая адресация возможна как «вперёд», так и «назад» относительно текущей команды. Не вдаваясь в тонкости, которые больше волнуют создателей ассемблер-систем, чем их пользователей, можно сказать, что относительная адресация - это просто обращение к ячейке памяти, помеченной меткой MET. Для ассемблеров БК-0010, имеющих механизм локальных меток, необходимо иметь в виду, что адресация (не только в этом, но и во всех других случаях, когда в составе операндов употребляется имя метки) возможна только с использованием обычных меток (начинающихся с буквы), а локальные пригодны лишь для указания адресов переходов в операторах ветвления и цикла. Это связано в основном с тем, что отличить локальную метку (начинающуюся с цифры) от числа (например, индекса) ассемблер-система не может. Пример:
            CLR     А1                  ; Адресация с использованием
            MOV     R2,BR2              ; меток:
            MOV     RGB,(R5)+           ; A1, BR2, RGB, TYP
            MOVB    #377,TYP
            MOVB    TYP,R4

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

  7. Относительная косвенная адресация. Аналогична предыдущему способу, но по адресу, определяемому меткой, находится не само число-операнд, а, как обычно и случаях косвенной адресации, его адрес. Обозначение: @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... число из ячейки с адресом, равным коду текущей команды! Чепуха, и ничего более.

Контрольные вопросы и задания

  1. Что будет записано в регистр R0 командой MOV R1,R0, если в регистре R1 содержится число 5555?
    • 5555.

  2. Что будет записано в регистр R5 последовательностью команд:
            MOV     #1000,@#2500
            MOV     #40000,@#1000
            MOV     @#2500,R4
            MOV     R4,R5
            MOV     @R5,R5
    • Число 40000.

  3. Что будет записано в R0 последовательностью команд (считая, что метка MET определена и находится в ОЗУ):
            MOV     #1000,MET
            MOV     #3402,@#1000
            MOV     #377,@#3400
            MOV     @MET,R5
            MOV     -(R5),R0
    • Число 377.

  4. Каким будет содержимое R2 после выполнения каждой из команд: MOVB #160,R2; MOV #277,R2;MOVB #201,R2? (Чтобы не ошибиться, «нарисуйте» полученные числав двоичной форме, поразрядно.)
    • 160, 277, 177601.

  5. Команда MOV -(PC),-(PC) «саморазмножается» в ОЗУ в сторону младших адресов («вниз» по памяти). Придумайте команду, которая будет исполнять «бег на месте», т.е. переписывать свой код по своему же адресу и передавать себе управление.
  6.         MOV     -(PC),@PC
  7. Придумайте последовательность двух одинаковых команд, исполняющую «бег на месте» (обязательно с использованием кодов обеих команд).
  8.         MOV     @PC,-(PC)
            MOV     @PC,-(PC)
  9. Придумайте команду, которая бы «саморазмножалась» по памяти «вверх».
    • Такой команды нет.

  10. Что запишет в R3 последовательность команд:
            MOVB    #255,R0
            CLR     MET
            MOVB    R0,MET
            MOV     (PC)+,R3
    MET:    CLR     R0
    • Число 255.

Оператор прямого присваивания. Выражения

После того, как изучены способы адресации, рассмотрим ещё два тесно примыкающих к ним вопроса. Мы уже знаем, что метками в тексте программы помечаются некоторые строки и что в процессе трансляции и компоновки метке присваивается текущий адрес (т.е. адрес начала строки в которой она стоит). Но как быть, если имеется в виду адрес в ПЗУ или в системной области? Может быть, в таких случаях придётся ограничиться только абсолютной адресацией, прямо указывая адрес? Это не всегда удобно, поэтому в ассемблере предусмотрен специальный механизм для определения меток, так называемый оператор прямого присваивания. Он записывается как 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

(При трансляции и компоновке вторым словом данных команд записывается результат вычисления выражений.)

Контрольные вопросы и задания

  1. Что запишется в регистр R1 в результате выполнения программы.
            MET=254
            MOV     #MET+4,R0
            MOV     #155,-(R0)
            MOVB    @#256,R1
    • Число 155.

  2. Какая ячейка будет обнулена в результате выполнения программы:
            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, С и значение каждого из них устанавливается равным нулю или единице в зависимости от результата выполнения очередной команды. Эти биты используются для реализации условных переходов и для некоторых других целей. Их значение устанавливается равным единице в следующих случаях:

В противных случаях соответствующие биты ССП равны нулю.

1. Вычислительные операторы

С одним операндом

Кроме проверки операнда оператор TST(B) иногда применяется для инкремента (или декремента) содержимого регистра на 2. Например, команда TST (R2)+ увеличивает на 2 содержимое R2, причём делает это быстрее, чем две команды INC R2, и занимает в ОЗУ всего одно слово. Однако при таком использовании данного оператора в регистре должно быть записано число, соответствующее реально существующему адресу ЭВМ. В противном случае, например если в регистре R2 записано число 177776 (не существующий в БК-0010 адрес), произойдёт прерывание по зависанию. (Это замечание, конечно, относится к любой команде, выполняемой по косвенному адресу, независимо от того, с какой целью она применяется.) Необходимо также отметить, что на некоторых ЭВМ с аналогичной системой команд косвенное обращение к слову по нечётному содержимому регистра тоже ведёт к зависанию. На БК-0010 вместо этого производится обращение по меньшему чётному адресу, т.е. адрес обращения корректируется. Содержимое же регистра после такой операции (при автоинкрементной или автодекрементной адресации), как обычно, изменяется на 2, т.е. остаётся нечётным.

(Оператор ADC(B) может быть использован для обеспечения многократной точности при сложении чисел:

        ADD     А0,B0               ; сложение младших частей
        ADC     В1                  ; прибавить перенос к старшей части
        ADD     А1,В1               ; сложение старших частей

SBC(B) аналогично используется для обеспечения многократной точности при вычитании:

        SUB     А0,B0
        SBC     В1
        SUB     A1,B1

В данном примере показано вычитание с двойной точностью. - Прим. ред.)

С двумя операндами

Что такое «исключающее или»? Это функция, фиксирующая несовпадение битов, так называемая функция неравнозначности. Поясним сказанное таблицей состояний.

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. Операторы управления программой

Эта группа операторов предназначена для управления ходом вычислительного процесса (организации ветвлений, подпрограмм и циклов).

Здесь, как и во всех других командах ветвления, адрес передачи управления определяется суммой текущего значения счётчика команд 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

В конце подпрограммы должна стоять команда возврата - 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 и т.п.) стек необходимо восстанавливать. В противном случае он рано или поздно будет исчерпан и ЭВМ перестанет работать. В самой же подпрограмме со стеком следует работать осторожно и заботиться, чтобы при выходе из неё адрес возврата находился в вершине стека.

Бывают случаи, когда при обращении к подпрограмме нужно передать ей ряд чисел - параметров. Эти числа при каждом обращении к одной и той же подпрограмме могут быть различными, и их может быть разное количество. В таких случаях довольно удобно перед вызовом заносить параметры в стек, извлечь же их в подпрограмме можно, например, с помощью индексной адресации или иным путём. Но так как параметров может быть разное количество, то каждый раз по выходе из подпрограммы стек надо восстанавливать. Команда 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. Но поскольку данная команда существует, разобрать её было необходимо хотя бы для того, чтобы избежать вопросов. А применять её или нет - дело сугубо личное. Большинство программирующих на ассемблере решают этот вопрос отрицательно и, может быть, правильно делают - при программировании и так хватает забот, даже без использования столь сложной по структуре и малоэффективной команды.

        ; "мигание" экрана
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 и 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 мы будем уделять больше внимания. Будем рассматривать их не в порядке номеров, а по функциональному назначению.

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, но и командные коды. Эти сведения достаточно полно изложены в прилагаемых к БК руководствах, и подробно останавливаться на них нет необходимости.

Заметим, что приведённая в последнем примере строка текста предварительно записывается в память отнюдь не оптимальным способом, но другого варианта мы пока ещё «не проходили».

Контрольные вопросы и задания

  1. Чему будет равно содержимое регистра R0 в результате исполнения последовательно каждой из команд:
            MOV     #77000,R0
            INC     R0
            COMB    R0
    • 77000; 77001; 77376.

  2. Чему будет равно содержимое регистра R5 в результате исполнения последовательно каждой из команд:
            MOV     #177777,R5
            ASL     R5
            ASL     R5
            ROL     R5
            ROL     R5
    • 177777, 177776; 177774; 177771, 177763.

  3. Что получится в результате выполнения команды XOR #1000,@#2400 ?
    • Ничего, так как первым операндом команды XOR может быть только RN - один из регистров общего назначения. Такая команда даже не будет оттранслирована ассемблером.

  4. Как переписать младший байт из регистра R0 в регистр R4, не изменяя при этом старшие байты R0 и R4 (остальные регистры и ячейки памяти не использовать)? Напишите соответствующую программу.
  5.         CLRB    R4
            BISB    R0,R4
  6. Как установятся биты условий ССП 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

  7. Будет ли выполняться ветвление по оператору BGE в данной программе:
            MOV     #377,R0
            MOV     #120,R1
            CMPB    R0,R1
            BGE     MET

    Что изменится, если вместо оператора CMPB использовать CMP?

    • Не будет, так как число 377 отрицательное и меньше числа 120, а оператор BGE учитывает знак. При замене CMPB на CMP ветвление будет выполняться

  8. Сколько раз будет выполнен цикл SOB в данном случае:
            MOV     #10,R0
    0:      DEC     R0
            SOB     R0,0
    • 4 раза, так как регистр-счётчик R0 дополнительно модифицируется в геле цикла.

  9. Сколько символов «А» выведет на экран следующая программа:
           MOV     'A',R0
    0:     MOV     #10,R1
           EMT     16
           SOB     R1,0
    • Бесконечное множество, так как цикл организован неправильно - запись константы в R1 включена в цикл.

  10. Напишите программу, которая по нажатию любой клавиши (кроме клавиши «СТОП») выводила бы на экран символ «?»
  11. 0:    EMT     6
            MOV     '?',R0
            EMT     16
            BR      0
  12. Напишите программу, которая преобразовывала бы символы, вводимые с клавиатуры в любом регистре («РУС», «ЛАТ», «СТР», «ЗАГЛ»), в символы в регистре «ЛАТ-ЗАГЛ» соответственно нажатой клавише (например, «л» - в «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

В этой части мы закончим рассмотрение EMT-функций БК-0010 и их роли в программировании на ассемблере.

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
0:      MOV     #2,R1               ; Вывод начиная с 3-й позиции
        EMT     6                   ; Пауза до нажатия клавиши
        MOV     #TEX,R2             ; Адрес начала текста
        MOV     #233,R0             ; Команда переключения
        EMT     16                  ; формата
        CLR     R0                  ; Очистка
        EMT     22                  ; служебной строки
        MOV     'Пр',(R2)+          ; Запись в ОЗУ
        MOV     'ив',(R2)+          ; строки символов –
        MOV     '',(R2)+          ; слова "Привет!"
        MOV     '! ',(R2)+
        CLRB    (R2)+               ; Нулевой байт - конец текста
        MOV     #TEX,R2             ; Адрес начала текста
1:      MOVB    (R2)+,R0            ; Вывод текста
        BEQ     0                   ; в служебную строку
        EMT     22
        INC     R1                  ; Программа зациклена
        BR      1
ТЕХ:    END
; "муаровый" узор
        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 мы уже знакомы.

        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 в конце программы нам понадобилось только потому, что мы ещё не успели познакомиться с командой останова процессора.)

Таблица ССД

Разряд ССД

Адрес байта

Включённый режим дисплея

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), так и на языках высокого уровня.

Адреса байтов

Содержимое

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-диспетчере БК-0010 предусмотрены также ещё 16 так называемых резервных входов EMT с номерами 52, 54, ... 110. При обращении к ним происходит передача управления по адресам, равным соответственно 160000, 160004, ... 160074. Эти входы могут быть использованы, если вместо тест-ПЗУ разместить, начиная с адреса 160000, ПЗУ пользователя. (При подключённом контроллере дисковода его ПЗУ попадает как раз в область адресов начиная с 160000, и тогда некоторые из названных EMT-функций можно использовать для вызова подпрограмм доступа к диску. Подробнее об этом см. в № 1 за 1993 г., с. 102. - Прим. ред.)

На этом рассмотрение командных прерываний EMT можно считать законченным. К вопросу о том, как использовать полученные знания, мы вернёмся позже, а пока продолжим изучение операторов ассемблера и первым разберём уже упомянутый ранее оператор 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. - Прим. ред.)

        MOV     (SP)+,PC            ; Восстановить адрес
                                    ; команды прерванной
                                    ; программы
        MTPS    (SP)+               ; Восстановить ССП

Операторы управления машиной

Прочие операторы

К данной группе относятся также команды изменения признаков, т.е. разрядов 00...03 ССП. Используются они редко.

(Существуют и другие машинные команды, обеспечивающие очистку или установку одновременно двух или трёх признаков, но они не имеют сокращённой ассемблерной записи. Их коды приведены в Приложении 4 к книге: Осетинский А.Г., Осетинский М.Г., Писаревский А.Н. Фокал для микро- и мини-компьютеров. Л.: Машиностроение, 1988. - Прим. ред.)

Псевдооператоры

Как уже неоднократно говорилось, каждый оператор с принадлежащими ему операндами (если они имеются) при трансляции переводится в машинный код, образуя машинную команду, или инструкцию. Но имеется ряд специфических задач, для решения которых обычных операторов недостаточно (например, резервирование места под константы, переменные и массивы, запись в память текстов (вы помните, как неуклюже мы делали это в наших прежних примерах?), символьных и цифровых последовательностей и т.п.) Этой цели служат псевдооператоры, или псевдокоманды.

В отличие от обычных операторов, псевдокоманды при трансляции не переводятся в машинные коды, они представляют собой лишь указания транслятору, что записать в память с текущего адреса или что выполнить при трансляции. Заносимая в память информация, если она имеется, следует после псевдооператора. Рассмотрим, какие псевдооператоры имеются в ассемблерах «МИКРО». Все они начинаются с символа «точка», который в ассемблере означает текущий адрес. Таким образом, любой псевдооператор можно истолковать как указание транслятору: «записать с текущего адреса...».

Каждая из перечисленных таким образом констант записывается в очередное машинное слово. Если константой является адрес метки, то его абсолютное значение транслятор заносит только после компоновки программы. В менее совершенных версиях ассемблеров допускается запись после .# только одного значения константы, но зато часто разрешается писать по несколько псевдооператоров в строке.

Этот псевдооператор широко применяется для записи констант, переменных (с заданием их исходного значения), а также строк абсолютных адресов меток, которые можно затем использовать для адресации различных модулей программы.

Приведём пример такой записи. Пусть в программе имеется 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

(В первоначальном тексте примера мы этот фрагмент не привели, заботясь о его максимальной простоте.)

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), то в качестве модулей можно использовать подпрограммы, после исполнения которых управление будет возвращаться в основную программу, - иногда такой способ удобнее.

        MOV     PC,R4               ; Занести текущий адрес
        ADD     (PC)+,R4            ; Прибавить смещение 
        .@MET+2                     ; Смещение с поправкой на +2

Мы получили абсолютный адрес метки MET в регистре R4. Каким образом это делается? Вначале мы записали в R4 текущий адрес. Во второй строке прибавили к этому адресу смещение, полученное с помощью псевдооператора .@ в третьей строке. Мы добились этого, косвенно обращаясь через регистр PC, в котором в этот момент находится адрес следующей команды, т.е. как раз нужной нам строки Но в момент обращения к PC в первой строке мы записали адрес, на 2 меньший, чем адрес строки псевдооператора, и вынуждены исправить эту ошибку, внеся поправку в смещение («+2»), То, что мы во второй строке не просто косвенно обращаемся к PC, а с инкрементом, служит важной цели - увеличить содержимое PC на 2 и таким образом «перескочить» строку с псевдооператором: передавать на неё управление нельзя, там не команда, а произвольное число. Пользуясь таким приёмом, можно получить адрес метки независимо от того, находится она до или после псевдооператора. Как всегда в таких случаях, метка должна быть глобальной, а не локальной. Возможны и иные приёмы получения абсолютных адресов меток с помощью данного псевдооператора, но предложенный - самый экономичный по расходу памяти и быстродействию.

Все вышеперечисленные псевдооператоры могут быть записаны как в одну строку друг за другом в любой последовательности, так и в отдельных строках, но при этом надо учитывать, что псевдокоманды .# и .@ могут правильно работать только при их трансляции по чётным адресам. В псевдооператоре .@ можно, в отличие от .#, указывать только одно имя метки, перечислять их через запятую нельзя. В записанной последовательности псевдооператоров недопустимы пробелы и прочие символы, разрывающие её. Такой символ либо ведёт к ошибке, либо считается концом строки - и всё, следующее за ним, транслятор игнорирует.

Символ

Код 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 (Кстати, обозначать метками начала текстовых сообщений, как это сделано в нашем примере для наглядности, совсем не обязательно. Это может пригодиться, только если потребуется ещё раз вывести то же сообщение в другом месте программы).

Контрольные вопросы и задания

  1. Напишите программу, которая бы «отслеживала» положение символьного курсора по горизонтали, перемещая наравне с ним стрелку в служебной строке.
    • 0:      EMT     6                   ; Ввод символа (команды)
              EMT     16                  ; Вывод символа на экран
              EMT     26                  ; Позиция курсора (R1 = X)
              CLR     R0                  ; Очистка
              EMT     22                  ; служебной строки
              MOV     #33,R0              ; Код "стрелка вниз"
              EMT     22                  ; Символ в служебную строку (позиция - в R1)
              BR      0                   ; Программа зациклена
              END                         ; Конец
  2. Напишите программу, которая бы, определяя установленный режим дисплея, «приказывала» пользователю печатать символы только в русском регистре.
    • 0:      EMT     6                   ; Ввод символа
              TSTB    @#43                ; Регистр "РУС"?
              BNE     1                   ; Да - печать символа,
              MOV     #TEX,R1             ; иначе – выдать
              CLR     R2                  ; предупреждение
              EMT     20                  ; о вводе не русского символа
              BR      0                   ; К началу
      1:      EMT     16                  ; Символ на экран
              BR      0                   ; Программа зациклена
      ТЕХ:    .A:^ Нажмите клавишу "РУС"!^
              .E
              END                         ; Конец
  3. Напишите простейший копировщик - программу, которая бы после запуска позволяла скопировать последний загруженный с магнитной ленты по «естественному» адресу файл без задания его имени, адреса и длины с клавиатуры. Программа должна быть перемещаемой, с целью размещения её в зоне ОЗУ, не занимаемой копируемым файлом.
    • Подсказка: блок параметров EMT 36 для записи не обязательно начинается с адреса 320 а программа должна получиться очень короткой - всего несколько операторов.

              MOV     #344,R1             ; Адрес блока параметров послед-
                                          ; него загруженного файла минус 2
                                          ; используем как блок параметров файла
                                          ; для записи
              MOV     #2,@R1              ; Команда "Запись"
              EMT     36                  ; Запись файла
              HALT                        ; Конец
              END
  4. Что произойдёт, если после запуска следующей программы нажать любую символьную клавишу:
            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, в совокупности с особенностями этой ЭВМ обеспечивает реальное быстродействие примерно 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

Адресация

Тип операнда и добавочное время, мкс

способ

код

А,А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 мкс. Для вычисления времени исполнения однооперандной команды к основному времени необходимо прибавить время адресации операнда из таблицы. Для примера вычислим время исполнения ряда команд:

Таблица 2

Команда

Тип операнда,
код адресации

Время исполнения осн.
+ добавочное, мкс

Сумма, мкс

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. Все эти сложности объясняются порядком, в котором процессор обрабатывает операнды на микропрограммном уровне, и уже отмеченными особенностями самой ЭВМ. Примеры:

Таблица 3

Команда

Тип операнда,
коды адресации

Время исполнения, осн.
+ добавочное, мкс

Сумма, мкс

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 не зависит от того, работает ли команда с байтом или со словом.

Ещё ряд операторов имеет время исполнения, которое должно быть оговорено особо.

Таблица 4

Оператор или группа операторов

Общее время исполнения, мкс

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 мкс).

Таблица 5

Прерывание 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 - число точек вектора (независимо от кода операции); время несколько варьирует в зависимости от направления черчения (наименьшее время - для горизонтальных и вертикальных векторов)

Контрольные вопросы и задания

  1. Что выгоднее с точки зрения скорости работы ЭВМ ставить на первое место в двухоперандных командах с разными способами адресации: «быстрый» или «медленный» операнд? Подсчитайте время исполнения команд CMP @MET,RN и CMP RN,@MET.
    • Медленный, при этом общая скорость исполнения команд заметно возрастает. Время - 13.4 и 16 мкс соответственно.

  2. Что исполняется быстрее: обращение к подпрограмме или переход к обработке командного прерывания?
    • Обращение к подпрограмме намного быстрее: JSR RN,N + RTS RN даже в самом худшем случае (код адресации 7) требует 26.8 мкс, а, например, EMT + RTI - 36.2 мкс. Это не говоря уже о том, что для обращения по EMT или TRAP необходимо потратить значительное время ещё и на извлечение и обработку команды программой-диспетчером .

  3. Подсчитайте, сколько времени займёт исполнение команд:
    MOV #12,R0
    EMT 16
    • Первая команда - 8 мкс, вторая, если не происходит рулонный сдвиг экрана, - 1.8 мс (при сдвиге - 18 мс). Очевидно, что по сравнению со второй командой время исполнения первой практически равно нулю.

  4. Как лучше (с точки зрения быстродействия ЭВМ) организовать ввод чисел-констант в программе:
    • ... каждый раз вписывая в нужное место программы операнд #Х (например, MOV #1000,R0),
    • ... составив таблицу констант и обращаясь к ней косвенно через регистр (например, MOV (R1)+,R0),
    • ... составив таблицу констант, обозначив их метками и обращаясь к ним по меткам (например, MOV MET,R0)?
    • Первым или вторым способом: они равнозначны по времени адресации, тогда как третий способ медленнее примерно в 1.5 раза. Но нужно учесть, что для обращения к таблице по второму способу необходимо ещё, как правило, предварительно занести в RN адрес константы в таблице. Поэтому явно предпочтительнее, если констант не слишком много, просто напрямую указывать их в качестве операндов, например MOV #1250.R0, а не составлять из них таблицы.

  5. Пусть нужно вывести текст из 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.4561ЛЕ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 кГц. Только надо учитывать, что при этом скорость обработки компьютером текущей задачи резко снижается, ибо почти всё время уходит на обработку прерываний.

Контрольные вопросы и задания

  1. Придумайте способ, который бы позволил обращаться с помощью команды IOT не к одной программе обработки, а к нескольким, как EMT.
    • Нужно после IOT записать в программу аргумент, например, так:

      IOT
      .#122

      Программа же обработки прерывания должна содержать своеобразный диспетчер, который прочитает аргумент из памяти, выполнит по нему нужную программу, а перед возвратом из прерывания увеличит в стеке содержимое PC на 2, чтобы «обойти» аргумент и не передать на него управление. Про то, что команду IOT лучше вообще не трогать, уже говорилось, но это не причина чтобы лишить читателя удовольствия поломать голову.

  2. Каково «нормальное» содержимое вектора прерывания @#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-стандарту. Для тех, кто не знаком с описанием этого принтера поясним коды команд:

30 - отмена строки, т.е. сброс «мусора» (CAN);

33 - регистровый код (ESCAPE, ESC);

100 - «@», ESC-код инициализации принтера;

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. Стоит ли?

Контрольные вопросы и задания

  1. Предложите способ, который позволит всю информацию, выводимую любой программой на экран, записать в виде файла на МЛ. Какое значение может иметь такой способ, например, для работы с Фокалом? С дизассемблером?
    • Нужно написать системный драйвер, аналогичный драйверу принтера для печати текста. Но вместо вывода символов на принтер надо выделить свободную область памяти, куда записывать последовательно коды появляющихся на экране символов. Количество байтов получившегося массива надо подсчитывать. Окончание формирования массива может быть по достижении им заданной длины или по специальной директиве, после чего с помощью EMT 36, зная адрес и длину массива, надо записать его на МЛ в виде файла. При работе с Фокалом такой приём даёт возможность, например, записать листинг Фокал-программы на МЛ, а затем загрузить его в редактор текста. Это пригодится, если нужно обработать описание или учебную программу, написанные на Фокале, включить Фокал-программу в текст, написанный с учебными целями, и т.п. При работе с дизассемблером это позволит дизассемблированный текст программы в кодах прокомментировать в редакторе, а отредактировав его и приведя к формату, например, МИКРО.10К, даже оттранслировать в ассемблер-системе или включить в качестве блока в свою программу[7].

  2. Напишите блок для «повторного», без разрушения памяти, выхода из МСД в Фокал, пользуясь указаниями о порядке такого выхода, приведёнными в конце раздела о директивах МСД. Предусмотрите защиту блока выхода в Фокале.
    • Для выхода из МСД в Фокал надо проделать следующее.

      Щ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. И наконец, со многими ячейками автор проводил эксперименты для определения их функционального назначения и возможного использования.

★ ★ ★

Другие ячейки (40-43, 50-53) при записи в них какого-либо числа включают режим частично и «ненормально» - дальнейшая работа с дисплеем становится невозможной или затруднительной. Но и эти ячейки могут быть использованы для каких-либо целей. Например, регистр «РУС»-«ЛАТ» можно переключать через ячейку 43. но при этом поменяется на обратную и установка регистра «СТР»-«ЗАГЛ». Ячейка 41 проявляет себя только в отдельных случаях, например после переключения цветов. Ячейка 40 блокирует выведение на экран каждого второго символа (выводятся широкие символы), а ячейка 42 по достижении края экрана блокирует рулонный сдвиг и как бы организует режим «РП» на обычном экране. Ячейки графических режимов 50-52 хотя и переключают дисплей в режим текстовой графики, но при этом на экране остаётся тень символьного и графического курсоров, резко замедляется отработка команд текстовой графики и т.п.

0:      CLR     @#156
        EMT     6
        EMT     16
        BR      0

0:      MOV     #77,@#156
        EMT     6
        EMT     16
        BR      0

Функция перевода строк на экране нарушается: при работе первой программы курсор возвращается к левому краю экрана, но всего на одну телевизионную строку экрана ниже, а при работе второй символ вообще печатается только на одном месте в начале строки.

В цветном режиме содержимое ячейки 156 кратно двум. Его можно использовать в различных программах, например, для индикации достижения текстом края экрана и организации автоматического перевода строки на принтере[11].

0:      MOV     #1001,@#162         ; Интервал вывода
        MOV     #TEX,R1             ; Начало текста
        CLR     R2                  ; Признак конца
                                    ; текста
        EMT     20                  ; Выдать текст
        EMT     6                   ; Ждать нажатия клавиши
        BR      0                   ; Программа зациклена
ТЕХ:    .A:Привет!
        .E
        END

Однако при использовании данного приёма необходимо учитывать, что по достижении «физического» конца экрана (который при сдвинутом рулоне может прийтись на любое место) и попытке «вывода» символов в область ПЗУ компьютер зависнет, так как соответствие номера символа и рулона нарушается.

Две последние ячейки могут широко использоваться в различных функциях графических редакторов и других программ с применением EMT 30 и EMT 32 для получения координат последней выведенной точки (конца вектора).

Две последние ячейки используются при выдаче в служебную строку индикаторов - надписей, указывающих на режимы дисплея: «РУС», «ЛАТ», «ГРАФ», «ЗАП», «СТИР», «БЛР», «ИСУ», «РЕД».

★ ★ ★

Наше повествование о системной области подходит к завершению. Осталось сказать несколько слов о стеке.

Как вы уже знаете, стек - это специально выделенная область памяти, куда сама ЭВМ или программы пользователя заносят данные, которые нужно сохранить (обычно временно). Адресуется стек чаще всего через специальный регистр R6, или SP, отличающийся от остальных регистров процессора тем, что его автоинкремент или автодекремент выполняется всегда на 2, независимо от того, работает команда со словом или с байтом.

Отсчёт ячеек стека, в отличие от всех прочих участков памяти, ведётся «сверху вниз», т.е. началом считается адрес 777 (или, в чётных адресах, 776), а концом (или «дном») - адрес 372. Если программа, использующая стек, не работает с магнитофоном или организует блок параметров для него начиная не с адреса 320, а в другом месте ОЗУ, то стек может быть «продлён» до адреса 320.

Ячейка, адрес которой соответствует текущему содержимому SP, называется вершиной стека. Адрес вершины при работе со стеком не остаётся постоянным, а всё время меняется. Но этот адрес всегда должен восстанавливаться при работе отдельных ветвей программы. Таким образом, если мы в начале программы заносим что-либо в стек, изменяя адрес его вершины, а затем программа ветвится. то мы должны восстановить адрес вершины до ветвления или предусмотреть одинаковый порядок его восстановления в каждой из ветвей (либо после выхода из них). Нарушение этого правила может привести к тому, что стек будет постоянно засоряться всё новыми данными и рано или поздно затрёт ячейки системной области. К чему это приведёт, ясно без лишних комментариев[16].

Стек в БК-0010 организуется по типу магазина для автоматического оружия, а обмен данных в нем - по принципу «первым вошёл - последним вышел». Данные, введённые раньше, оказываются в стеке дальше от вершины (и «выше» в памяти), чем введённые позже, и данные, введённые последними, извлекаются из стека прежде всего. Но это всего лишь традиция и удобный приём обращения к стеку. Не составляет большого труда организовать хранение данных и по другому принципу, например «первым вошёл - первым вышел» («очередь»), но это, как правило, менее удобно.

Обычно данные сохраняются в стеке с помощью стандартного приёма - последовательной засылкой операторами MOV с автодекрементной адресацией регистра R6 (последовательное извлечение с помощью автоинкрементной адресации). По ходу изучения ассемблера приводилось столько примеров с использованием стека, что повторять их нет необходимости. Мы познакомились также ещё с двумя возможностями сохранения и использования данных в стеке: с помощью косвенной индексации (чтобы «достать» данные, лежащие не на вершине) и малоупотребительной команды MARK. Но есть ещё один способ использования стека для хранения данных - с помощью абсолютной адресации.

При реализации этого способа исходят из того, что для работы подавляющего большинства пользовательских программ такая «глубина» стека, которая имеется на БК-0010, не нужна. Вполне достаточно иметь «дно» стека по адресу 600, а иногда даже 700. При этом всю расположенную ниже область памяти можно рассматривать как системные ячейки, не используемые драйверами монитора БК-0010. Естественно, что любая из этих ячеек может быть использована программистом для своих нужд. Например, этот приём широко применяется в МИКРО.10К: начиная с адреса 400 по 600 многие ячейки используются для хранения констант и переменных ассемблер-системы. Перечислим некоторые из них, они могут быть полезными при работе с МИКРО.10К:

Разумеется, используются в МИКРО и другие ячейки. Благодаря такой организации системных переменных МИКРО.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].

Контрольные вопросы и задания

  1. Принесло ли вам пользу знакомство с ячейками системной области БК-0010?
    • Каждый отвечает так, как считает нужным.

  2. Напишите программу генератора случайных чисел, использующую системный таймер БК-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. 3 .Какой приём вы можете предложить для перевода восьмеричного числа в десятичное?
    • Простейший приём такой. Нужно вычислить значения «круглых» десятичных чисел - 10, 100, 1000, 10000 в восьмеричном представлении и затем вычитать их из исходного числа, начиная с самого большого для одного регистра - с 10000, подсчитывая каждый раз количество вычитаний, пока ещё нет переноса. Это количество, переведённое в код КОИ-8 (если его нужно выдать на экран), и будет десятичным разрядом. Затем нужно восстановить остаток и продолжить вычитание уже следующего числа, например 1000, и так до 1. (Восьмеричные эквиваленты приведённых десятичных чисел- 23420, 1750, 144, 12.)

  4. Написать программу, обеспечивающую перекодирование десятичного числа из его текстовой записи (т.е. строки символов «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» - переопределение локальной метки, которая раньше не индицировалась и доставляла много хлопот.

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

Сложнее дело обстоит с логическими ошибками, когда программа формально написана правильно, но работает не так. как ожидалось. В этом случае, если обычный просмотр текста и проверка алгоритма не выявляют ошибок, следует разбить программу на функциональные блоки и проверять их по отдельности. Частая ошибка - неправильное ветвление программы. Она может быть вызвана непониманием логики работы программы, неправильным оператором (например, применение оператора «со знаком», когда нужен «без знака») или ошибками при сравнении операндов. На последнем случае стоит остановиться подробнее.

Когда ветвление задаётся после оператора сравнения, то оно может происходить неправильно по следующим причинам:

Кроме перечисленных возможны, конечно, и другие причины, но эти - самые распространённые. Не менее частая причина ошибок ветвления - передача управления «не туда», т.е. когда соответствующая метка находится не на том месте, где должна бы быть. Например в цикле может всё время повторно заноситься исходное значение цикловой переменной, обнуляться регистры или повторно задаваться исходный адрес массива и т.д.

Уточнить, как именно происходит ветвление и где программа «зацикливается», а также отследить прочие «сомнительные точки» можно разными приёмами. Нагляднее и проще всего на время включать в текст программы в точках перехода по ветвлению операторы действие которых сразу заметно: 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 бод при высокой надёжности и поддерживает большинство программ пользователя и языков высокого уровня. При размещении такого драйвера в дополнительном ОЗУ удобство работы с магнитофоном возрастает столь заметно, что необходимость подключения дисковода становится неочевидной... на некоторое время.

«Сколько на свете людей, столько и мнений»: старая поговорка права всегда, и, в полном соответствии с ней, мнение редакции журнала в