262
Глава семнадцатая
Записываем
результат
10h
20h
21h
FFh
0000h:
Сохранить
11h
Загрузить
Сложить
Вычесть
Остановить
56h
2Ah
38h
0000h:
Данные
Коды
После выполнения команды Загрузить в аккумуляторе запи-
сано число 56h. После команды Сложить в нем содержится
сумма 56h и 2Ah, т. е. 80h. При выполнении команды Вычесть
биты следующего по порядку числа в массиве Данные (38h)
инвертируются. Инвертированное значение C7h складывает-
ся с числом 80h, при этом на вход сумматора для переноса (CI)
подается 1.
C7h
+80h
+1h
48h
Окончательный результат равен 48h (в десятичной системе
86 + 42 – 56 = 72).
Настало время обратиться к проблеме недостаточной раз-
рядности сумматора и подключенных к нему устройств. Рань-
ше я говорил лишь о наиболее прямолинейном решении этой
проблемы: включить в схему еще один 8-битовый сумматор
(и еще одну защелку, и еще один набор лампочек и т. д.), что-
бы в итоге получить 16-битовое устройство.
Но есть и более экономичное решение. Рассмотрим в каче-
стве примера сумму 16-разрядных чисел:
76ABh
+232Ch
Сложить два этих 16-битовых числа есть то же самое, что по
отдельности сложить их младшие байты:
ABh
+2Ch
D7h
263
Автоматизация
и старшие байты:
76h
+23h
99h
Получаем 99D7h. Если мы расположим 16-разрядные числа в
памяти таким образом:
Старший
байт результата
10h
20h
11h
20h
0000h:
Загрузить
10h
Загрузить
Сложить
Сохранить
Сложить
ABh
2Ch
Данные
Коды
11h
Сохранить
FFh
Остановить
76h
23h
Младший
байт результата
0000h:
число D7h будет сохранено по адресу 0002h, а 99h — по адресу
0005h.
Конечно, такая последовательность действий далеко не все-
гда будет приводить к желаемому результату. В данном кон-
кретном примере она сработала, но что если придется склады-
вать числа 76ABh и 236Ch? В этом случае сложение двух млад-
ших байтов приведет к появлению переноса:
ABh
+6Ch
117h
Это перенос нужно добавить к сумме старших байтов:
1h
+76h
+23h
9Ah
чтобы получить окончательный результат — 9A17h.
Можно ли усовершенствовать наш автоматический сум-
матор, чтобы он корректно складывал 16-разрядные числа?
Можно. Для этого нужно лишь сохранить бит переноса после
264
Глава семнадцатая
первого сложения, а затем подать его на вход сумматора для
переноса при втором сложении. Как сохранить этот бит? Ко-
нечно, в 1-битовой защелке, которую мы назовем защелкой для
переноса.
Для работы с защелкой для переноса нам понадобится еще
одна команда. Назовем ее Сложить с переносом. При сложе-
нии 8-битовых чисел вы используете обычную команду Сло-
жить. Вход CI сумматора равен 0, а выход CO сохраняется в
защелке для переноса (хотя в данном случае запоминать его и
не нужно).
Суммируя 16-битовые числа, вы сначала складываете их
младшие байты с помощью обычной команды Сложить. Вход
CI сумматора и в этом случае равен 0, а выход CO сохраняется
в защелке для переноса. Для сложения старших байтов вы при-
меняете команду Сложить с переносом. При ее выполнении
на вход сумматора для переноса подается содержимое защел-
ки для переноса. Если первое суммирование привело к появ-
лению переноса, он используется во втором суммировании.
Если переноса не было, выход защелки для переноса равен 0.
Для вычитания 16-битовых чисел нам понадобится еще
одна новая команда — Вычесть с заимствованием. Для выпол-
нения обычной команды Вычесть надо инвертировать вычи-
таемое и установить в 1 вход сумматора для переноса. При этом
обычно 1 равен и выход сумматора для переноса, но на это
при обычном вычитании можно закрыть глаза. Другое дело
— вычитание 16-битовых чисел. Тут значение выхода для пе-
реноса надо сохранять в защелке для переноса. При втором
вычитании содержимое этой защелки подается на вход CI сум-
матора.
С учетом двух новых команд мы набрали уже 7 кодов:
Команда
Код
Загрузить
10h
Сохранить
11h
Сложить
20h
Вычесть
21h
Сложить с переносом
22h
Вычесть с заимствованием
23h
Остановить
FFh
265
Автоматизация
При выполнении команд Вычесть и Вычесть с заимствовани-
ем число, направляемое в сумматор, инвертируется. Выход CO
сумматора является входом защелки для переноса. Его значе-
ние сохраняется в защелке при выполнении команд Сложить,
Вычесть, Сложить с переносом и Вычесть с заимствованием.
Вход сумматора для переноса устанавливается в 1 при выпол-
нении команды Вычесть или при выполнении команд Сложить
с переносом и Вычесть с заимствованием при условии, что 1
равен выход защелки для переноса.
Не забывайте, что при выполнении команды Сложить с
переносом вход сумматора для переноса устанавливается в 1,
только если к появлению переноса привело выполнение пре-
дыдущей команды Сложить или Сложить с переносом. При
сложении многобайтовых чисел всегда следует применять ко-
манду Сложить с переносом независимо от того, появится при
суммировании перенос или нет. В разобранном выше приме-
ре правильная последовательность команд такова:
10h
20h
11h
22h
0000h:
Загрузить
10h
Загрузить
Сложить
Сохранить
ABh
2Ch
Данные
Коды
11h
Сохранить
FFh
Остановить
76h
23h
0000h:
Старший
байт результата
Младший
байт результата
Сложить с переносом
Она корректно сложит любые 16-разрядные числа.
Всего две новые команды, но как они расширили возмож-
ности машины! Мы более не связаны необходимостью рабо-
тать только с 8-битовыми числами. Многократное примене-
ние команды Сложить с переносом позволит нам суммиро-
вать 16-битовые, 24-битовые, 32-битовые, 40-битовые значе-
ния и т. д. Например, для сложения 32-битовых чисел
7A892BCDh и 65A872FFh нам понадобится одна команда Сло-
жить и три команды Сложить с переносом:
266
Глава семнадцатая
10h
20h
11h
22h
0000h:
Загрузить
10h
Загрузить
Сложить
Сохранить
Сложить с переносом
CDh
FFh
Данные
Коды
11h
Сохранить
10h
Загрузить
2Bh
72h
0000h:
Следующий
за младшим
байт
результата
89h
22h
A8h
11h
10h
7Ah
22h
65h
11h
FFh
Сложить с переносом
Сохранить
Загрузить
Сложить с переносом
Сохранить
Остановить
Младший
байт
результата
Предыдущий
перед
старшим
байт
результата
Старший
байт
результата
Конечно, заносить эти числа в память не ахти как интересно.
Мало того, что придется изрядно пощелкать переключателя-
ми, нужно еще и следить за тем, чтобы числа вводились в нуж-
ные ячейки. Число 7A892BCDh, например, занимает адреса
0000h, 0003h, 0006h и 0009h, начиная с младшего байта. А ре-
зультат сложения придется извлекать из ячеек 0002h, 0005h,
0008h и 000Bh.
Кроме того, сумматор в его теперешнем виде не допускает
повторного использования результатов вычислений. Скажем,
сложили вы три 8-битовых числа, а затем вычли из суммы еще
одно 8-битовое число. Это легко сделать командами Загрузить,
Сложить, Сложить, Вычесть и Сохранить. Но если вы теперь
решите вычесть из суммы другое 8-битовое число, вам придет-
ся считать ее заново, так как нигде в памяти она не сохранилась.
Проблема нашего сумматора в том, что в нем доступ к ячей-
кам массивов Данные и Коды осуществляется одновременно
и последовательно, начиная с адреса 0000h. Каждой команде в
массиве Коды соответствует ячейка массива Данные, располо-
женная по такому же адресу. Величину, записанную в массив
Данные командой Сохранить, вернуть обратно в аккумулятор
нельзя.
267
Автоматизация
Для решения этой проблемы я намерен внести в сумматор
фундаментальное изменение, которое на первый взгляд кажет-
ся довольно сложным. Но со временем вы поймете (надеюсь!),
какие просторы оно перед нами открывает.
Поехали! Итак, у нас сейчас 7 команд.
Команда
Код
Загрузить
10h
Сохранить
11h
Сложить
20h
Вычесть
21h
Сложить с переносом
22h
Вычесть с заимствованием
23h
Остановить
FFh
Пока каждый из кодов занимает в памяти 1 байт. Но я своей
властью выделяю каждому из них, кроме кода Остановить, по
3 байта памяти. В первом будет находиться сам код, а в двух
оставшихся — 16-битовый адрес ячейки памяти. В команде За-
грузить, например, это будет адрес ячейки, число из которой
нужно загрузить в аккумулятор. В командах Сложить, Вычесть,
Сложить с переносом и Вычесть с заимствованием по этому
адресу будет храниться число, которое нужно прибавить к со-
держимому аккумулятора или вычесть из него. В команде Со-
хранить в этих двух байтах будет указываться адрес ячейки, в
которую нужно записать содержимое аккумулятора.
Рассмотрим в качестве примера простейшую задачу, кото-
рую в состоянии решить наш сумматор, — сложение пары
чисел. Для ее решения вы заполняете массивы Данные и Коды
такими значениями:
10h
20h
11h
0000h:
Остановить
FFh
Загрузить
Сложить
Сохранить
4Ah
B5h
Данные
Коды
0000h:
Сумма
В модернизированном сумматоре все команды, кроме Ос-
тановить, занимают по 3 байта:
268
Глава семнадцатая
10h
00h
00h
00h
0000h:
Сложить байт по адресу 0001h
и содержимое аккумулятора
20h
Загрузить в аккумулятор
байт по адресу 0000h
Коды
01h
11h
Сохранить содержимое
аккумулятора по адресу 0002h
00h
02h
FFh
Остановить
0003h:
0006h:
0009h:
За каждой командой, кроме Остановить, следуют два байта,
указывающие 16-битовый адрес ячейки в массиве Данные. В
данном случае это адреса 0000h, 0001h и 0002h.
Чуть раньше я показал вам, как командами Сложить и Сло-
жить с переносом найти сумму двух 16-битовых чисел —
76ABh и 232Ch. При этом младшие байты слагаемых мы дол-
жны были записать в ячейки 0000h и 0001h, а старшие — в
ячейки 0003h и 0004h. Результат сложения оказывался в ячей-
ках 0002h и 0005h.
С учетом нашего нововведения слагаемые и результат мож-
но разместить в памяти более понятным способом, причем в
той ее области, куда мы еще не забирались.
76h
ABh
Данные
4000h:
Сюда запишется старший байт суммы
23h
2Ch
Сюда запишется младший байт суммы
4002h:
4004h:
269
Автоматизация
В таком порядке я разместил все числа исключительно для
удобства. На самом деле они могут быть разбросаны по всем
64 К памяти в произвольном порядке. Для сложения чисел,
сохраненных в указанных выше ячейках, в массив Коды мы
должны ввести такие команды:
10h
40h
01h
40h
0000h:
Сложить байт по адресу
4003h и содержимое
аккумулятора
20h
Загрузить в аккумулятор
байт по адресу 4001h
Коды
Коды
03h
11h
40h
05h
10h
40h
00h
21h
Загрузить в аккумулятор
байт по адресу 4000h
Сложить с переносом
байт по адресу 4002h и
содержимое аккумулятора
40h
02h
11h
Сохранить содержимое
аккумулятора
по адресу 4004h
40h
04h
FFh
Остановить
0003h:
0006h:
0009h:
000Ch:
000Fh:
0012h:
Сохранить содержимое
аккумулятора
по адресу 4005h
Сначала суммируются два младших байта, расположенные по
адресам 4001h и 4003h, а их сумма записывается в ячейку 4005h.
Старшие (из ячеек 4000h и 4002h) — суммируются командой
Сложить с переносом, а сумма сохраняется по адресу 4004h.
Теперь мы можем убрать команду Остановить и записать в
массив Коды дополнительные команды. Во всех последующих
вычислениях можно будет использовать как сами слагаемые,
так и результат их суммирования, просто обращаясь к соот-
ветствующим ячейкам памяти.
Чтобы воплотить эту систему в жизнь, мы должны под-
ключить к выходам массива Коды три 8-битовых защелки. В
каждой будет храниться один из байтов 3-байтовой команды.
В первую защелку попадет код команды, во вторую — стар-
ший байт адреса, в третью — младший. Выход второй и тре-
тьей защелок станет 16-битовым адресом ячейки в массиве
Данные:
270
Глава семнадцатая
Clk
Clr
Addr
DO
Пульт
управления
Пульт
управления
16
8
8-битовая
защелка
8-битовая
защелка
8-битовая
защелка
8
8
8
16
8
8
8
Коды
Addr
DO
8
DI
8
Дан-
ные
16-битовый
счетчик
Clk
W
Clk
Clk
64K
×
8
RAM
64K
×
8
RAM
Процесс извлечения команды из памяти называется выбор-
кой команды (instruction fetch). В нашем сумматоре каждая ко-
манда занимает по 3 байта и извлекается из памяти побайто-
во. Выборка одной команды занимает три цикла синхронизи-
рующего сигнала, а полный командный цикл — четыре цикла
синхронизирующего сигнала. Из-за этого система управляю-
щих сигналов, конечно, усложнится.
Слова «машина выполнила команду» подразумевают, что
в машине реализованы некие действия, зашифрованные ко-
дом команды. Это, разумеется, не означает, что машина живая
и способна анализировать код, чтобы решить, что делать даль-
ше. Назначение кода команды в том, чтобы генерировать уни-
кальную последовательность управляющих сигналов, которые
приводят к определенным последствиям.
Обратите внимание, что расширение возможностей маши-
ны отрицательно сказалось на ее быстродействии. При той же
частоте вибратора она занимается сложением вчетверо доль-
ше, чем исходная версия сумматора. Это следствие инженер-
ного принципа «Бесплатных обедов не бывает», который оз-
начает, что усовершенствование машины в одном отношении
приводит к ее ухудшению в чем-то другом.
Если бы вы в самом деле собирали эту машину из реле,
большую часть схемы составляли бы, очевидно, два 64-кило-
байтных массива памяти. Конечно, вы можете решить, что для
начала вам вполне хватит двух массивов объемом по 1 кб, и
271
Автоматизация
скорее всего так оно и есть. Но нельзя ли как-нибудь обойтись
одним массивом? Вообще-то можно. Я разбил память на два
массива — для данных и для кодов — с единственной целью:
сделать архитектуру автоматического сумматора максималь-
но простой и понятной. Но теперь, когда мы увеличили длину
команд до 3 байтов, используя второй и третий байты для ука-
зания адреса в памяти, надобность в двух отдельных массивах
отпала. И коды, и данные можно хранить в одном и том же
массиве.
Для этого нам понадобится селектор 2 линии на 1, кото-
рый будет определять способ адресации массива RAM. Один
адрес, как и раньше, поступает на селектор с 16-битового счет-
чика. Выход массива данных по-прежнему подключен к трем
защелкам, в которых сохраняется код команды и сопровожда-
ющие его два байта адреса. Этот 16-битовый адрес также по-
дается на вход селектора. Когда в защелки записывается адрес,
селектор пропускает его на адресный вход массива RAM:
Clk
Clr
Addr
DO
Пульт
управления
Селектор «2 на 1»
16
16
16
8
8
8
8
8
16
8
8
8
8
DI
Коды
Дан-
ные
16-битовый
счетчик
W
Clk
Clk
Clk
Sel
8-битовая
защелка
8-битовая
защелка
8-битовая
защелка
64K
×
8
RAM
Как мы продвинулись! Теперь можно вводить команды и
данные в один и тот же массив RAM. Вот как, например, мож-
но сложить два 8-битовых числа и вычесть из суммы третье:
272
Глава семнадцатая
10h
00h
10h
00h
0000h:
Сложить байт по адресу 0011h
и содержимое аккумулятора
20h
Загрузить в аккумулятор байт
по адресу 0010h
11h
21h
Вычесть байт по адресу 0012h
из содержимого аккумулятора
00h
12h
11h
00h
13h
FFh
Сохранить содержимое аккумулятора
по адресу 0013h
Остановить
..
.
45h
A9h
8Eh
000Ch:
0010h:
Ячейка для окончательного
результата
Как обычно, размещение команд начинается с адреса 0000h,
так как именно с этого значения начинает считать 16-битовый
счетчик. Заключительная команда Остановить находится в
ячейке 000Ch. Три обрабатываемых числа и результат можно
было разместить в памяти где угодно, кроме, конечно, первых
13 ячеек, занятых командами. Я решил занять для них ячейки,
начиная с адреса 0010h.
Теперь допустим, что вам понадобилось прибавить к ре-
зультату еще два числа. Что ж, можно заменить только что
введенные команды новыми. Но не исключено, что вы пред-
почтете просто продлить уже введенную последовательность,
заменив команду Остановить в ячейке по адресу 000Ch новой
командой Загрузить. За ней последуют две команды Сложить,
команда Сохранить и команда Остановить. Но вот беда — на-
чиная с адреса 0010h, память занята данными. Придется пере-
273
Автоматизация
двинуть их дальше, соответствующим образом отредактиро-
вав команды, в которых эти данные используются.
Да-а-а, думаете вы. Наверное, с объединением данных и
кодов в одном массиве мы поторопились. Но уверяю вас, что
проблема такого рода возникла бы рано или поздно в любом
случае, поэтому мы должны ее решить. Попробуем, не сдви-
гая старые данные, разместить новые команды, начиная с ад-
реса 0020h, а новые данные — начиная с адреса 0030h:
10h
00h
13h
00h
0020h:
Сложить байт по адресу 0030h
и содержимое аккумулятора
20h
Загрузить в аккумулятор байт
по адресу 0013h
30h
20h
00h
31h
11h
00h
32h
FFh
Сохранить содержимое аккумулятора
по адресу 0032h
Остановить
43h
2Fh
0030h:
Ячейка для окончательного
результата
..
.
Сложить байт по адресу 0031h
и содержимое аккумулятора
Заметьте: первая команда Загрузить обращается по адресу
0013h, где сохранен результат предыдущего вычисления.
Теперь память заполнена так: по адресу 0000h — команды, по
адресу 0010h — данные, по адресу 0020h — опять команды, а по
адресу 0030h — снова данные. Мы хотим, чтобы сумматор авто-
матически выполнил все команды, начав работу с ячейки 0000h.
Прежде всего нужно убрать из ячейки 000Ch команду Ос-
тановить. Проблема в том, что записанная по этому адресу
274
Глава семнадцатая
величина будет интерпретироваться как код команды. Та же
судьба постигнет и величину в ячейке, отстоящей от 000Ch на
3 байта, т. е. 000Fh, а также и в следующих ячейках с 3-байто-
вым интервалом — 0012h, 0015h, 0018h, 001Bh и 001Eh. Что
если один из этих байтов случайно окажется равен 11h — коду
команды Сохранить, а два байта за ним — 00h и 23h? Машина
послушно запишет содержимое аккумулятора в ячейку с ад-
ресом 0023h. Но эта ячейка содержит важную для нас инфор-
мацию! Даже если ничего подобного и не случится, нас все рав-
но ждут неприятности. С точки зрения машины, следующая
за 001Eh команда расположена в ячейке 0021h, а не 0020h, где
она находится на самом деле.
Все согласны с тем, что нельзя просто убрать из ячейки
000Ch команду Остановить и уповать на лучшее?
Мы должны заменить ее на новую команду, а именно коман-
ду Перейти, которую уже давно пора включить в наш репертуар.
Команда
Код
Загрузить
10h
Сохранить
11h
Сложить
20h
Вычесть
21h
Сложить с переносом
22h
Вычесть с заимствованием
23h
Перейти
30h
Остановить
FFh
Обычно адресация ячеек в автоматическом сумматоре проис-
ходит последовательно. Команда Перейти позволяет нарушить
это правило. После ее выполнения адресация массива RAM
начинается с нового адреса. Команды такого рода называют
еще командами ветвления (branch).
В нашем примере командой Перейти нужно заменить
команду Остановить в ячейке 000Ch:
30h
00h
20h
000Ch:
Перейти к команде по адресу 0020h
275
Автоматизация
Величина 30h — это код команды Перейти. За ней располага-
ется 16-битовый адрес ячейки, где содержится следующая
команда, которую должен выполнять сумматор.
В нашем примере в начале работы сумматор выполняет
команду в ячейке 0000h — Загрузить. Затем исполняются
команды Сложить, Вычесть и Сохранить. Следующая — Пе-
рейти — заставляет сумматор прервать эту последовательность
и перейти к команде в ячейке 0020h. Это команда Загрузить, за
которой следуют Сложить, Сложить, Сохранить и наконец
Остановить.
Команда Перейти действует на 16-битовый счетчик. Вся-
кий раз, когда она встречается в последовательности команд,
на выходе счетчика должен появляться стоящий в ней адрес.
Это осуществляется с помощью входов Pre и Clr триггера
D-типа со срабатыванием по фронту,
Q
D
Clk
Q
Pre
Clr
который, как вы помните, является основным компонентом
счетчика. Обычно входы Pre и Clr равны 0. Если сигнал Pre
равен 1, в 1 обращается и сигнал Q. Если 1 равен сигнал Clr,
сигнал Q обращается в 0.
Чтобы записать в триггер новое значение (будем называть
его А — адрес), его нужно включить в такую схему:
Q
Сброс
D
Clk
Q
Pre
Clr
Задать
A
276
Глава семнадцатая
Как правило, сигнал Задать равен 0. В этом случае вход Pre
триггера также равен 0. Если сигнал Сброс не равен 1, нулю
равен и сигнал Clr. Вход Сброс нужен, чтобы триггер можно
было очистить независимо от значения сигнала Задать. Если
сигнал Задать равен 1 и сигнал А тоже равен 1, сигнал Pre об-
ратится в 1, а Clr — в 0. Если сигнал А равен 0, сигнал Pre обра-
тится в 0, а Clr — в 1. Иными словами, значение выхода Q со-
впадает со значением на входе A.
В 16-битовом счетчике нам понадобится 16 таких схем. За-
грузите в счетчик новое значение, и он будет считать, начиная
с него.
Других серьезных изменений в схеме пока нет. 16-битовый
адрес, переписанный из памяти в защелки, подается как на вход
селектора «2 на 1» (откуда он попадает на адресный вход мас-
сива RAM), так и на вход 16-битового счетчика.
Clk
Clr
Addr
DO
Пульт
управления
Селектор «2 на 1»
16
16
16
8
8-битовая
защелка
8-битовая
защелка
8-битовая
защелка
8
8
8
8
16
8
8
8
8
DI
Коды
Дан-
ные
Задать
Сброс
16
Sel
16-битовый
счетчик
W
Clk
Clk
Clk
64K
×
8
RAM
Разумеется, сигнал должен обращаться в 1 лишь при выпол-
нении двух условий: код команды равен 30h и адрес перехода
сохранен в защелках.
Команда Перейти очень полезна. Но несравненно полез-
нее команда, осуществляющая переход лишь при выполнении
определенного условия. Такой переход называется условным
277
Автоматизация
(conditional jump). Чтобы проиллюстрировать его необходи-
мость, зададимся простым вопросом: способен ли наш сумма-
тор перемножить два 8-битовых числа, например, A7h и 1Ch?
Произведение двух 8-битовых чисел является 16-битовым
числом. Для простоты будем считать, что 16-битовыми явля-
ются и сомножители. Для начала решим, куда поместить ис-
ходные числа и их произведение.
00h
A7h
00h
00h
1000h:
16-битовый сомножитель
1Ch
16-битовый сомножитель
00h
1002h:
1004h:
16-битовое произведение разместится здесь
и здесь
В десятичном счислении 1Ch равно 28. Нет нужды доказы-
вать вам, что умножение A7h на 1Ch равносильно сложению
28 чисел A7h. Поэтому в ячейках 1004h и 1005h на самом деле
будет размещен 16-битовый результат этих многократных сло-
жений. Вот как выглядит набор кодов для размещения по это-
му адресу первой суммы:
10h
10h
05h
10h
0000h:
Сложить байт по адресу
1001h и содержимое
аккумулятора
20h
Загрузить
в аккумулятор байт
по адресу 1005h
01h
11h
Сохранить содержимое
аккумулятора
по адресу 1005h
10h
05h
10h
10h
04h
22h
Сложить байт по адресу
1000h и содержимое
аккумулятора
Сохранить содержимое
аккумулятора по адресу
1004h
04h
00h
11h
10h
0012h:
10h
..
.
Загрузить в аккумулятор
байт по адресу 1004h
0003h:
0006h:
000Fh:
000Ch:
0009h:
В начале работы ячейки 1004h и 1005h содержат нули. После
выполнения шести этих команд размещенное в них 16-бито-
278
Глава семнадцатая
вое значение равно A7h умножить на 1. Чтобы в ячейках 1004h
и 1005h оказалось нужное значение, ту же последовательность
кодов следует исполнить еще 27 раз. Достичь этого можно,
скопировав последовательность 27 раз, начиная с адреса 0012h,
или же разместить по этому адресу команду Остановить и 28
раз нажать кнопку Сброс.
Конечно, и тот, и другой вариант на практике использо-
вать неудобно. В обоих случаях от вас требуется некое действие:
ввести набор кодов или нажать кнопку Сброс, причем ровно
столько раз, на сколько нужно умножить число. Один-два раза
— куда ни шло, но перемножать числа таким способом всю
жизнь охотников найдется немного.
А что если разместить по адресу 0012h команду Перейти?
После ее выполнения счетчик снова начнет счет с 0000h:
30h
00h
00h
0012h:
Перейти к команде по адресу 0000h
Вроде бы то, что нужно. После первого выполнения кодов
ячейки 1004h и 1005h содержат значение, равное произведе-
нию A7h и 1. Затем команда Перейти осуществляет переход к
началу фрагмента, и после следующего его выполнения в ячей-
ках 1004h и 1005h содержится число A7h, умноженное на 2. В
конце концов машина доберется и до произведения A7h и 1Ch,
но, увы, в нужный момент не остановится!
Мы хотим, чтобы команда Перейти срабатывала только нуж-
ное число раз, т. е. совершала условный переход. Добиться это-
го на самом деле не так сложно. Для начала добавим в схему 1-
битовую защелку, похожую на защелку для переноса, и назо-
вем ее нулевой защелкой, так как ее содержимое будет равняться
1, только если все выходы 8-битового сумматора равны 0.
Флажок
нуля
DI
Clk
DO
Clr
8-битовый сумматор
279
Автоматизация
Выход 8-входового вентиля ИЛИ-НЕ равен 1, только если все
входы равны 0. Как и в защелке для переноса, синхронизиру-
ющий сигнал Clk в нулевой защелке вызывает запоминание
входного сигнала только при выполнении команд Сложить,
Вычесть, Сложить с переносом и Вычесть с заимствованием.
Величина, хранимая в нулевой защелке, называется флажком
нуля (Zero flag). Не запутайтесь: флажок нуля равен 1, если все
выходы сумматора равны 0; флажок нуля равен 0, если на не-
которых выходах сумматора имеются 1.
Защелка для переноса и нулевая защелка позволяют рас-
ширить набор доступных действий на 4 команды.
Команда
Код
Загрузить
10h
Сохранить
11h
Сложить
20h
Вычесть
21h
Сложить с переносом
22h
Вычесть с заимствованием
23h
Перейти
30h
Перейти если 0
31h
Перейти если перенос
32h
Перейти если не 0
33h
Перейти если не перенос
34h
Остановить
FFh
Например, команда Перейти если не 0 вызывает переход на
заданный адрес, только если выход нулевой защелки равен 0.
Иначе говоря, перехода не будет, если результат последней
команды Сложить, Вычесть, Сложить с переносом или Вычесть
с заимствованием равен 0. Для реализации этой команды дос-
таточно расширить набор управляющих сигналов, применяе-
мых для обычной команды Перейти. При выполнении коман-
ды Перейти если 0 сигнал Задать 16-битового счетчика уста-
навливается в 1, только если флажок нуля равен 0.
Имеющегося в нашем распоряжении набора команд хва-
тит для написания кодов, умножающих одно число на другое:
280
Глава семнадцатая
10h
10h
03h
00h
0012h:
Сложить байт по адресу 001Eh
и содержимое аккумулятора
20h
Загрузить в аккумулятор байт
по адресу 1003h
1Eh
11h
Сохранить содержимое аккумулятора
по адресу 1003h
10h
03h
33h
00h
00h
FFh
Остановить
001Eh:
Перейти к команде по адресу 0000h,
если флажок нуля не равен 1
0015h:
0018h:
001Bh:
Как мы уже установили, к началу этого фрагмента в ячейках
0004h и 0005h содержится 16-битовое число A7h, умноженное
на 1. Первая команда фрагмента загружает в аккумулятор число
из ячейки 1003h, т. е. 1Ch. Оно складывается с содержимым
ячейки 001Eh. Да, в ячейке 001Eh записан код команды Оста-
новить, но это не мешает ему быть и просто обычным чис-
лом. Сложить FFh и 1Ch — это все равно что вычесть из 1Ch
единицу. В результате получается 1Bh. Это не 0, поэтому фла-
жок нуля равен 0. Число 1Bh записывается обратно в ячейку
1003h. Далее следует команда Перейти если не 0. Поскольку
результат действительно не равен 0, условие перехода соблю-
дается, и следующей будет выполнена команда по адресу 0000h.
Не забывайте: команда Сохранить на значение флажка нуля
не влияет. Он задается лишь при выполнении команд Сложить,
Вычесть, Сложить с переносом и Вычесть с заимствованием и
сохраняет значение до следующего выполнения одной из этих
команд.
При втором выполнении фрагмента ячейки 0004h и 0005h
содержат произведение числа A7h на 2. Число 1Bh прибавля-
ется к FFh, и получается 1Ah. Это не 0, поэтому вновь проис-
ходит возвращение к началу.
281
Автоматизация
На 28-й раз ячейки 0004h и 0005h содержат произведение
чисел A7h и 1Ch. По адресу 1003h хранится число 1. Его сумма с
числом FFh равна 0, и флажок нуля наконец-то устанавливает-
ся в 1! Условие команды Перейти если не 0 не выполняется, пе-
рехода нет, и следующей командой становится Остановить. Есть!
Наступает великий миг: я утверждаю, что собранное нами
устройство может с полным правом именоваться компьютером!
Очень примитивным, но компьютером. Его ключевое свойство
— наличие команды условного перехода. Именно возможность
управляемых циклических процедур отличает компьютер от
калькулятора. Я только что продемонстрировал, как наша ма-
шина с помощью условного перехода умножила одно число на
другое. Подобным образом можно осуществить и деление. Да-
лее, действие нашей машины не ограничено 8-битовыми чис-
лами. Она способна складывать, вычитать, умножать и делить
16-битовые, 24-битовые, 32-битовые числа и числа большей
разрядности. А значит, ей подвластны квадратные корни, лога-
рифмы и тригонометрические функции.
Раз уж мы собрали компьютер, пора начать говорить о нем,
как о компьютере.
Разработанное нами устройство относится к цифровым
(digital) компьютерам, поскольку работает с дискретными циф-
рами. Это отличает его от аналоговых (analog) компьютеров,
которые существовали некоторое время назад, а сейчас прак-
тически исчезли. Напомню: дискретными называются данные,
способные принимать лишь набор фиксированных значений.
Аналоговые данные непрерывны и могут принимать любое
значение из заданного интервала.
Цифровой компьютер состоит из 4 основных компонен-
тов: процессора, памяти, минимум одного устройства ввода и
минимум одного устройства вывода. В нашей машине память
состоит из 64-килобайтового массива RAM. Роль устройств
ввода и вывода играют ряды переключателей и лампочек на
пульте управления памятью. Они позволяют нам (исполняю-
щим в этом шоу роли людей) вводить в память числа и про-
сматривать результаты вычислений.
Все остальное — процессор (processor) или, как его еще
называют, центральное процессорное устройство (Central
Processor Unit) — ЦПУ (CPU). Процессор часто называют моз-
гом компьютера, но я стараюсь не прибегать к подобным срав-
282
Глава семнадцатая
нениям, поскольку лично мне собранная схема мозг не напо-
минает. В наши дни часто приходится слышать слово микро-
процессор. Это тот же процессор, только маленький-малень-
кий, благодаря изобретениям, о которых я расскажу в главе
18. К нашему релейному детищу приставка «микро» вряд ли
подходит!
Процессор, который мы собрали, является 8-разрядным.
8 битам равна ширина аккумулятора и большинства каналов
данных. Из 16 битов состоит только адрес в массиве RAM. Если
бы он был 8-битовым, мы могли бы адресовать не 65 536, а
лишь 256 байтов — тут не разгуляешься.
Процессор тоже состоит из нескольких компонентов. Я уже
неоднократно упоминал аккумулятор — защелку для хране-
ния чисел внутри процессора. 8-битовый инвертор и 8-бито-
вый сумматор вместе составляют арифметико-логическое ус-
тройство (Arithmetic Logic Unit) — АЛУ (ALU). В нашем слу-
чае оно способно выполнять только арифметические действия,
а именно сложение и вычитание. В несколько более сложных
компьютерах (с которыми мы еще познакомимся) арифмети-
ко-логическое устройство выполняет также логические опера-
ции — И, ИЛИ и исключающее ИЛИ. 16-битовый счетчик на-
зывается программным счетчиком (program counter).
Наш компьютер состоит из реле, проводов, переключате-
лей и лампочек. Все вместе они называются аппаратным обес-
печением (hardware) компьютера. Команды и другие числа,
которые мы вводили в память, составляют его программное
обеспечение (software).
В разговорах о компьютерах программное обеспечение
обычно называют просто программами, а процесс их создания
— программированием. Придумывая набор команд, которые
позволили бы нашему компьютеру умножать одно число на
другое, именно программированием я и занимался.
Обычно в компьютерных программах различают код (code),
т. е. собственно команды, и данные (data), т. е. числа, с которы-
ми работает код. Иногда это различие не столь очевидно. На-
пример, в разобранном нами примере код команды Остано-
вить одновременно играет роль числа –1. Коды команд (на-
пример, 10h для команды Загрузить), на основании которых
процессор выполняет то или иное действие, называются ма-
шинными кодами или машинным языком. Слово «язык» впол-
283
Автоматизация
не оправданно, так как именно с помощью кодов человек
«объясняет» машине, что она должна сделать.
До сих пор я называл команды, выполняемые машиной,
довольно длинными словами и целыми фразами, например,
Сложить с переносом. Но на практике для обозначения команд
используют двух- трехбуквенные английские сокращения —
мнемонические коды, — записываемые прописными буквами.
Вот как может выглядеть набор мнемокодов для нашего ком-
пьютера.
Команда
Английское название
Код
Мнемокод
Загрузить
Load
10h
LOD
Сохранить
Store
11h
STO
Сложить
Add
20h
ADD
Вычесть
Subtract
21h
SUB
Сложить
Add with Carry
22h
ADC
с переносом
Вычесть
Subtract with Borrow
23h
SBB
с заимство-
ванием
Перейти
Jump
30h
JMP
Перейти
Jump If Zero
31h
JZ
если 0
Перейти
Jump If Carry
32h
JC
если
перенос
Перейти
Jump If Not Zero
33h
JNZ
если не 0
Перейти
Jump If Not Carry
34h
JNC
если не
перенос
Остановить
Halt
FFh
HLT
Удобство мнемокодов станет более очевидным, если мы
присовокупим к ним еще пару обозначений. Например, усло-
вимся писать вместо многословной инструкцию «Загрузить в
аккумулятор байт по адресу 1003h» короткое выражение:
LOD A,[1003h]
284
Глава семнадцатая
Обозначения A и [1003h], стоящие справа от мнемокода, на-
зываются аргументами. Аргументы конкретизируют выпол-
нение команды. Например, в команде Загрузить они указыва-
ют, куда (аргумент слева) следует загрузить данные (А означа-
ет аккумулятор) и откуда (аргумент справа) их следует извлечь.
Квадратные скобки означают, что в аккумулятор надлежит заг-
рузить не число 1003h, а содержимое ячейки памяти по адресу
1003h.
Подобным же образом инструкцию «Сложить байт по ад-
ресу 001Eh и содержимое аккумулятора» можно сократить до:
ADD A,[001Eh]
а инструкцию «Сохранить содержимое аккумулятора по адре-
су 1003h» — до:
STO [1003h],A
Заметьте: в команде STO мы также слева указываем, куда со-
храняются данные, и справа — откуда они извлекаются: со-
держимое аккумулятора записывается в ячейку 1003h. Нако-
нец, команда «Перейти к команде по адресу 0000h, если фла-
жок нуля не равен 1» превращается в краткое указание:
JNZ 0000h
Здесь квадратные скобки не применяются, поскольку переход
всегда осуществляется только по адресу. Числом аргумент этой
команды быть не может.
С учетом всех обозначений команды удобно записывать в
столбик, причем обводить их рамками уже не нужно; они и
без этого прекрасно читаются. Адрес, по которому хранится
команда (точнее, ее первый байт), мы будем указывать шест-
надцатеричными числом с двоеточием:
0000h: LOD A,[1005h]
А вот так будут обозначаться записанные в память данные:
1000h: 00h, A7h
1002h: 00h, 1Ch
1004h: 00h, 00h
Перечисление байтов через запятую означает, что первый байт
записан в ячейке, адрес которой указан слева, а второй байт —
285
Автоматизация
в следующей за ней ячейке. Эти три строчки можно было бы
заменить одной:
1000h: 00h, A7h, 00h, 1Ch, 00h, 00h
Целиком программа для умножения выглядит так:
0000h:
LOD A,[1005h]
ADD A,[1001h]
STO [1005h],A
LOD A,[1004h]
ADC A,[1000h]
STO [1004h],A
LOD A,[1003h]
ADD A,[001Eh]
STO [1003h],A
JNZ 0000h
001Eh:
HLT
1000h:
00h, A7h
1002h:
00h, 1Ch
1004h:
00h, 00h
Пробелы и пустые строки расставлены здесь с единственной
целью — сделать программу более понятной.
При написании кодов численные значения адресов лучше
не использовать, так как они могут измениться. Если вы, на-
пример, решите хранить данные, начиная с адреса 2000h, а не
1000h, вам придется переписывать многие команды. Для обо-
значения ячеек памяти предпочтительнее пользоваться мет-
ками (labels):
BEGIN:
LOD A,[RESULT + 1]
ADD A,[NUM1 + 1]
STO [RESULT + 1],A
LOD A,[RESULT]
ADC A,[NUM1]
STO [RESULT],A
286
Глава семнадцатая
LOD A,[NUM2 + 1]
ADD A,[NEG1]
STO [NUM2 + 1],A
JNZ BEGIN
NEG1:
HLT
NUM1:
00h, A7h
NUM2:
00h, 1Ch
RESULT:
00h, 00h
Метки NUM1, NUM2 и RESULT ссылаются на ячейки, храня-
щие по 2 байта. В приведенной выше программе выражения
NUM1 + 1, NUM2 + 1 и RESULT + 1 ссылаются на второй байт
соответствующей метки. Обратите внимание на метку NEG1
у команды HLT: она означает «negative one», т. е. «минус один».
Наконец, на случай, если вы забудете, зачем нужна та или
иная команда, в программе предусмотрены комментарии. Они
пишутся на простом человеческом языке и отделяются от кода
точкой с запятой:
BEGIN:
LOD A,[RESULT + 1]
ADD A,[NUM1 + 1]
; Сложить младшие байты
STO [RESULT + 1],A
LOD A,[RESULT]
ADC A,[NUM1]
; Сложить старшие байты
STO [RESULT],A
LOD A,[NUM2 + 1]
ADD A,[NEG1]
; Уменьшить число на 1
STO [NUM2 + 1],A
JNZ BEGIN
NEG1:
HLT
NUM1:
00h, A7h
NUM2:
00h, 1Ch
RESULT:
00h, 00h
287
Автоматизация
Язык программирования такого типа называется языком ас-
семблера (assembly language). Это своеобразный компромисс
между бессловесными машинными кодами и многословными
командами на «человеческом» языке, дополненный текстовы-
ми ссылками на ячейки памяти. Язык ассемблера и машинные
коды часто путают, поскольку это просто различные способы
представления одной и той же программы. Каждому оператору
языка ассемблера соответствует определенный числовой код.
Если бы вам пришлось писать программу для компьютера,
разработанного в этой главе, вы, наверное, предпочли бы сна-
чала написать ее на языке ассемблера (и на бумаге). Затем, когда
вы решили бы, что программа в основном написана и готова к
тестированию, вам пришлось бы вручную ассемблировать ее,
т. е. переводить операторы ассемблера в соответствующие чис-
ловые машинные коды, опять же на бумаге. Далее машинные
коды вводились бы в память компьютера с помощью переклю-
чателей, и программу можно было бы запускать, т. е. указать
компьютеру выполнить все составляющие ее команды.
После знакомства с концепцией программирования самое
время поговорить об ошибках. Совершать их при написании
программ, особенно, в машинных кодах, очень легко. Нехо-
рошо, конечно, когда в программе используются неверные
числа. Но что если с ошибкой вы введете код команды, напри-
мер, наберете с помощью переключателей 11h (код команды
Сохранить) вместо 10h (код команды Загрузить)? Компьютер
не только не выполнит нужное действие, т. е. не загрузит чис-
ло в аккумулятор, но и выполнит ненужное — запишет по-
верх этого числа теперешнее содержимое аккумулятора.
Последствия некоторых ошибок бывают непредсказуемы,
например, когда по адресу, указанному в команде Перейти,
отсутствует корректный код команды или когда командой Со-
хранить в ячейку с кодом команды по ошибке записывается
число. Произойти в таких случаях может (и происходит) все
что угодно.
Ошибка имеется даже в моей программе для умножения.
Если вы запустите ее во второй раз, она умножит A7h на 256 и
запишет результат поверх уже посчитанного произведения.
Причина понятна: после первого выполнения программы в
ячейке 1003h записан 0. Запустите программу еще раз, и к нему
будет прибавлено число FFh. Результат не равен 0, поэтому
выполнение программы продолжится.
288
Глава семнадцатая
Мы убедились, что наша машина умеет умножать, а зна-
чит, и делить. Я заявил также, что эти простейшие операции
позволяют находить корни, логарифмы и тригонометричес-
кие функции. На аппаратную часть компьютера возлагаются
три обязанности: сложение, вычитание и условный переход.
Как говорят программисты: «Для остального напишем про-
грамму».
Конечно, программа эта может оказаться очень сложной.
Об алгоритмах решения конкретных вычислительных задач
написано множество книг. Но нам пока рано задумываться об
этом. Мы ведь до сих пор ограничивались целыми числами,
не взяв на себя смелость подумать о дробях. Но мы еще вер-
немся к ним — в главе 23.
Я несколько раз говорил о том, что все приборы, нужные
для сборки компьютера, изобретены более 100 лет назад. Но
это, конечно, не означает, что уже тогда создание подобного
устройства было возможно. Многие принципы, рассмотрен-
ные нами в этой главе, даже в эпоху появления первых релей-
ных компьютеров (в 1930-е годы) все еще не были известны.
Додуматься до них люди смогли лишь в середине XX в. До того
времени многие изобретатели, например, упорно пытались
создать устройства, работающие не с двоичными, а с десятич-
ными числами. Не сразу оформилась и идея совместного хра-
нения кода и данных. Часто предпочитали вводить програм-
му в компьютер по мере ее выполнения с помощью перфо-
ленты. С памятью на заре компьютерной эры вообще были
большие трудности. Сборка 64 К памяти из 5 млн. реле сто лет
назад была такой же абсурдной задачей, как и сейчас.
Настало время немного оторваться от практических упраж-
нений и коротко познакомиться с историей появления счет-
ных устройств и машин. Не исключено, что в конце концов
окажется, что мы напрасно возились с нашим релейным ком-
пьютером. В главе 12 я уже говорил, что на смену реле при-
шли вакуумные лампы и транзисторы. Мы узнаем также, что
за это время люди научились делать разработанные нами про-
цессор и память совсем-совсем миниатюрными.
|