Код. Тайный язык информатики — страница 31 из 71

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

Получается, мы можем избавиться от лампочек, подключенных к защелке. Вместо этого выход защелки необходимо подключить ко входу DI массива RAM для записи сумм в память.

На этой схеме отсутствуют некоторые другие компоненты сумматора, в частности осциллятор и переключатель «Очистка». Я убрал их, поскольку уже не столь очевидно, откуда на входы счетчика и защелки поступают сигналы Clr и Clk. Более того, теперь, когда мы задействовали входы DI массива RAM, нам нужен способ управления его сигналом «Запись».

Давайте на мгновение оставим схему и сосредоточимся на стоящей перед нами задаче. Итак, мы хотим сконструировать сумматор, возможности которого не ограничиваются сохранением текущей суммы складываемых чисел, хотим полностью контролировать количество слагаемых, а также количество разных сумм, сохраняемых в памяти для последующего изучения.

Предположим, нам требуется сложить три числа, потом два, а затем еще три. Мы могли бы сохранить эти числа в массиве RAM, начиная с адреса 0000h, чтобы содержимое памяти выглядело следующим образом.

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

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

Первая версия сумматора выполняла единственное действие — сложение содержимого ячейки памяти со значением в 8-битной защелке, которую я назвал аккумулятором. Теперь необходимо, чтобы сумматор выполнял четыре различных действия. Для начала суммирования потребуется, чтобы сумматор передал байт из памяти в аккумулятор.

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

Давайте подробно распишем действия, которые должен выполнить сумматор в данном конкретном примере:

загрузить значение из ячейки 0000h в аккумулятор;

сложить значение из ячейки 0001h со значением в аккумуляторе;

сложить значение из ячейки 0002h со значением в аккумуляторе;

сохранить содержимое аккумулятора в ячейке 0003h;

загрузить значение из ячейки 0004h в аккумулятор;

сложить значение из ячейки 0005h со значением в аккумуляторе;

сохранить содержимое аккумулятора в ячейке 0006h;

загрузить значение из ячейки 0007h в аккумулятор;

сложить значение из ячейки 0008h со значением в аккумуляторе;

сложить значение из ячейки 0009h со значением в аккумуляторе;

сохранить содержимое аккумулятора в ячейке 000Ah;

остановить работу сумматора.

Обратите внимание: как и в исходной версии сумматора, адресация байтов памяти происходит последовательно, начиная с ячейки 0000h. Исходный сумматор просто прибавлял содержимое ячейки памяти по этому адресу к содержимому аккумулятора. В некоторых случаях это действие по-прежнему уместно. Однако иногда нам требуется загрузить значение из памяти непосредственно в аккумулятор или сохранить содержимое аккумулятора в памяти. После выполнения всех этих операций мы хотим, чтобы сумматор прекратил работу и можно было бы проверить содержимое памяти.

Как это реализовать? Важно понимать, что мы не можем просто записать в память кучу чисел и ожидать, что сумматор догадается, что с ними делать. Для каждого числа в массиве RAM необходимо предусмотреть некий числовой код, соответствующий операциям «Загрузка», «Сложение», «Сохранение» и «Остановка».

Вероятно, проще всего (и, разумеется, затратнее) хранить эти коды в отдельном массиве RAM. Доступ к этому второму массиву осуществляется одновременно с доступом к исходному массиву. Однако вместо слагаемых он будет содержать коды, указывающие на то, что должен сделать сумматор с содержимым соответствующей ячейки исходного массива RAM. Исходный массив можно обозначить словом «Данные», а новый массив — словом «Код».

Мы уже выяснили, что наш новый сумматор должен записывать суммы в исходный массив «Данные». Однако в новый массив «Код» значения будут сохраняться только при помощи пульта управления.

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

Операция

Код

Загрузить

10h

Сохранить

11h

Сложить

20h

Остановить

FFh

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

Возможно, вам захочется сравнить содержимое этого массива RAM с массивом, в котором хранятся слагаемые. В результате вы заметите, что каждый код в массиве «Код» соответствует значению в массиве «Данные», которое должно быть загружено в аккумулятор, прибавлено к его содержимому или сохранено в памяти. Используемые таким образом числовые коды часто называются кодами команд или кодами операций. Они дают схеме «команду» выполнить определенную «операцию».

Как я уже упоминал, выход 8-битной защелки исходного сумматора должен быть входом массива RAM «Данные». Так работает команда «Сохранить». Однако нам требуется внести еще одно изменение: изначально выход 8-битного сумматора — вход 8-битной защелки. Теперь для выполнения команды «Загрузить» выход массива «Данные» иногда должен соединяться со входом 8-битной защелки. Для этого необходим селектор двух линий на одну. Пересмотренная схема сумматора выглядит следующим образом.

На этой схеме еще чего-то не хватает, но она отображает все 8-битные потоки данных между различными компонентами. Шестнадцатибитный счетчик предоставляет адреса для двух массивов RAM. Выход массива «Данные» подключен ко входу 8-битного сумматора для выполнения команды «Сложить». Правда, ко входу 8-битной защелки может быть подключен либо выход массива «Данные» (в случае выполнения команды «Загрузить»), либо выход сумматора (в случае выполнения команды «Сложить»). В этой ситуации требуется селектор «2 на 1». Выходной сигнал защелки не только возвращается обратно в сумматор, но и подается на вход массива «Данные» для выполнения операции «Сохранить».

На схеме не хватает только контролирующих эти компоненты сигналов, которые называются управляющими; к ним относятся входы Clk и Clr 16-битного счетчика, входы Clk и Clr 8-битной защелки, вход W массива «Данные» и вход Sel селектора «2 на 1». Некоторые из этих сигналов, очевидно, будут основываться на выходе массива «Код». Например, вход Sel селектора «2 на 1» должен быть равен 0 (выбран выход «Данные» массива RAM), если выходной сигнал массива «Код» соответствует команде «Загрузить». Вход W массива «Данные» должен быть равен 1 только тогда, когда код соответствует команде «Сохранить». Эти управляющие сигналы могут генерироваться различными комбинациями логических вентилей.

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

Операция

Код

Загрузить

10h

Сохранить

11h

Сложить

20h

Вычесть

21h

Остановить

FFh

Коды команд «Сложить» и «Вычесть» отличаются только младшим битом значения, который мы будем называть C0. Если значение кода команды равно 21h, то схема должна делать то же самое, что и в случае выполнения команды «Сложить», за исключением того, что данные из массива «Данные» инвертируются перед попаданием в сумматор, а для входа сумматора CI задается значение 1. Сигнал C0 может выполнять обе операции в обновленном сумматоре, дополненном инвертором.

Предположим, нам нужно сложить два числа: 56h и 2Ah, а затем из полученной суммы вычесть 38h. Это можно сделать, используя следующие коды и данные, хранящиеся в двух массивах RAM.

После выполнения операции «Загрузить» аккумулятор содержит значение 56h, после операции «Сложить» — сумму 56h и 2Ah, то есть 80h. Операция «Вычесть» приводит к инвертированию битов следующего значения в массиве «Данные» (38h). Инвертированное значение C7h прибавляется к 80h, при этом вход сумматора для переноса (CI) равен 1.

Результатом будет 48h (в десятичной системе счисления: 86 + 42 – 56 = 72).

Еще одной нерешенной проблемой остается недостаточная ширина канала данных сумматора и всех остальных подключенных к нему устройств. Ранее я предлагал удвоить количество 8-битных сумматоров (и всех остальных компонентов), чтобы получить 16-битные устройства.

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