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

Для получения суммы двух 16-битных чисел отдельно сложим их младшие байты (крайние справа).

А затем старшие (крайние слева).

В результате получится число 99D7h. Если мы сохраним два 16-битных числа в памяти, как показано на рисунке, результат D7h будет сохранен по адресу 0002h, а 99h — по адресу 0005h.

Разумеется, это будет работать не всегда. Такой метод подходит для сложения чисел, выбранных в качестве примера. Если нам требуется сложить числа 76ABh и 236Ch, при сложении двух младших байтов возникает перенос.

Этот перенос должен быть прибавлен к сумме двух старших байтов для получения окончательного результата — 9A17h.

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

Использование защелки для переноса требует еще одного кода команды — «Сложить с переносом». При сложении 8-битных чисел используется обычная команда «Сложить». На вход сумматора для переноса (CI) подается значение 0, а значение, возникающее на выходе для переноса (CO), сохраняется в защелке для переноса (хотя в его использовании вообще нет необходимости).

При суммировании двух 16-битных чисел используем обычную команду «Сложить» для сложения младших байтов. Вход сумматора CI равен 0, а значение выхода CO сохраняется в защелке для переноса. Для сложения двух старших байтов будем использовать новую команду «Сложить с переносом». В данном случае при сложении двух чисел на вход сумматора CI подается значение, сохраненное в защелке для переноса. Таким образом, если в результате первой операции сложения возник перенос, этот бит переноса используется при втором сложении. Если переноса не возникло, выходное значение защелки для переноса равно 0.

Для вычитания одного 16-битного числа из другого потребуется еще одна новая команда — «Вычесть с заимствованием». Как правило, выполнение команды «Вычесть» предполагает инвертирование вычитаемого и подачу на вход сумматора CI значения 1. В этом случае выход для переноса также равен 1, — это нормально, на это явление можно не обращать внимания. Однако при вычитании 16-битного числа значение этого выхода необходимо сохранить в защелке для переноса. При втором вычитании это значение должно быть подано на вход сумматора CI.

Учитывая две новые операции, «Сложить с переносом» и «Вычесть с заимствованием», в общей сложности мы имеем семь кодов команд.

Операция

Код

Загрузить

10h

Сохранить

11h

Сложить

20h

Вычесть

21h

Сложить с переносом

22h

Вычесть с заимствованием

23h

Остановить

FFh

Число, подаваемое в сумматор, инвертируется при выполнении операции «Вычесть» или «Вычесть с заимствованием». Выходной сигнал сумматора CO подается на вход защелки для переноса. Он сохраняется в защелке всякий раз, когда выполняются операции «Сложить», «Вычесть», «Сложить с переносом» или «Вычесть с заимствованием». Значение входа для переноса 8-битного сумматора устанавливается в 1 при выполнении операций «Вычесть», или «Сложить с переносом», или «Вычесть с заимствованием», когда выход защелки для переноса равен 1.

В результате выполнения команды «Сложить с переносом» значение входа сумматора для переноса бывает равно 1 только в том случае, когда при выполнении предыдущей команды «Сложить» или «Сложить с переносом» на выходе из сумматора возник перенос. Таким образом, мы используем команду «Сложить с переносом» всякий раз, когда складываем многобайтные числа, вне зависимости от того, есть ли в этой операции необходимость. Вот как следует закодировать продемонстрированную ранее операцию сложения 16-битных чисел.

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

Добавление двух новых кодов команд значительно расширило функционал сумматора. Мы больше не ограничиваемся сложением 8-битных значений. Многократное использование команды «Сложить с переносом» позволяет складывать 16-, 24-, 32-, 40-битные значения и т. д. Предположим, нам нужно сложить 32-битные числа 7A892BCDh и 65A872FFh. Для этого потребуется лишь одна команда «Сложить» и три команды «Сложить с переносом».

Конечно, вводить эти числа в память не очень удобно. При этом не только приходится использовать переключатели для представления двоичных чисел. Сами числа сохраняются в несмежных ячейках, например 7A892BCDh оказывается в ячейках 0000h, 0003h, 0006h и 0009h, начиная с младшего байта. Для получения окончательного результата необходимо проверить значения, хранящиеся в ячейках 0002h, 0005h, 0008h и 000Bh.

Более того, текущая конструкция нашего сумматора не допускает повторного использования результатов в последующих операциях. Допустим, нужно сложить три 8-битных числа, а затем вычесть из этой суммы другое 8-битное число и сохранить результат. Для этого потребуются команда «Загрузить», две команды «Сложить», команды «Вычесть» и «Сохранить». А если нужно вычесть из этой исходной суммы другие числа, в то время как сумма эта недоступна, поскольку каждый раз нам пришлось бы ее пересчитывать? Проблема в том, что созданный нами сумматор обращается к ячейкам массивов «Код» и «Данные» одновременно и последовательно, начиная с адреса 0000h. Каждая команда в массиве «Код» соответствует ячейке массива «Данные» по тому же адресу. Когда в результате выполнения команды «Сохранить» в массиве «Данные» сохраняется некоторое значение, в дальнейшем оно уже не может быть загружено в аккумулятор.

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

Итак, в настоящее время у нас есть семь кодов команд.

Операция

Код

Загрузить

10h

Сохранить

11h

Сложить

20h

Вычесть

21h

Сложить с переносом

22h

Вычесть с заимствованием

23h

Остановить

FFh

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

В пересмотренной версии сумматора для хранения каждой команды, кроме «Остановить», требуется три байта.

За каждым из кодов команд, кроме «Остановить», следуют два байта, указывающие 16-битный адрес в массиве «Данные». В данном примере этими тремя адресами являются 0000h, 0001h и 0002h, однако они могут быть какими угодно.

Ранее я продемонстрировал процесс сложения двух 16-битных чисел, в частности 76ABh и 232Ch, с использованием команд «Сложить» и «Сложить с переносом». Однако нам пришлось сохранить два младших байта этих чисел в ячейках 0000h и 0001h и два старших байта — в ячейках 0003h и 0004h. Результат сложения был сохранен в ячейках 0002h и 0005h.

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

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

Обратите внимание: сначала складываются два младших байта, хранящихся в ячейках 4001h и 4003h, а результат сохраняется в ячейке 4005h. Два старших байта (содержащихся в ячейках 4000h и 4002h) складываются с использованием команды «Сложить с переносом», а результат сохраняется в ячейке 4004h. Если удалим команду «Остановить» и добавим новые команды в массив «Код», то при выполнении последующих расчетов сможем использовать исходные слагаемые и их сумму путем простого обращения к соответствующим адресам.

Ключевым моментом при реализации этой идеи является подключение выхода массива «Код» DO к трем 8-битным защелкам. В каждой из этих защелок хранится один из байтов 3-байтной команды. Первая защелка содержит код команды, вторая — старший байт адреса, третья — младший байт адреса. Выход второй и третьей защелок становится 16-битным адресом ячейки в массиве «Данные».

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

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

Важно: расширение функционала сумматора приводит к замедлению его работы. При той же частоте осциллятора машина складывает числа в четыре раза медленнее по сравнению с первым сумматором, описанным в этой главе. Это проявление инженерного принципа «Бесплатных завтраков не бывает»