кр гаряев. Чарльз Петцольд - Код_ тайный язык информатики-Манн, Иванов и Фе. Книга принадлежит Контакты владельца Культовая книга талантливого преподавателя стала для многих первым уверенным шагом в программировании
Скачать 6.11 Mb.
|
Операция Код Загрузить 10h Сохранить 11h Сложить 20h Остановить FFh Итак, чтобы выполнить три операции сложения из приведенного выше примера, нужно использо- вать пульт управления для сохранения следующих значений в массиве «Код». Возможно, вам захочется сравнить содержимое этого массива RAM с массивом, в котором хранятся слагаемые. В результате вы заметите, что каждый код в массиве «Код» соответствует значению в массиве 10h 20h 20h 10h 20h 10h 20h 20h 0000h: 0004h: 0007h: Сохранить Сохранить Сохранить 11h 11h 11h FFh Загрузить Сложить Сложить Загрузить Сложить Загрузить Сложить Сложить Остановить 000Bh: 240 Код «Данные», которое должно быть загружено в аккумулятор, прибавлено к его содержимому или сохранено в памяти. Исполь зуемые таким образом числовые коды часто называются кодами команд или кодами операций. Они дают схеме «команду» выполнить определенную «операцию». Как я уже упоминал, выход 8-битной защелки исходного сумматора дол- жен быть входом массива RAM «Данные». Так работает команда «Сохранить». Однако нам требуется внести еще одно изменение: изначально выход 8-бит- ного сумматора — вход 8-битной защелки. Теперь для выполнения команды «Загрузить» выход массива «Данные» иногда должен соединяться со входом 8-битной защелки. Для этого необходим селектор двух линий на одну. Пере- смотренная схема сумматора выглядит следующим образом. Clk Clr Clk Clr Sel 8 8 8 CI DO 8 Addr DO 16 DI Селектор «2 на 1» 8 Addr W 16-битный счетчик Пульт управления Пульт управления «Код» «Данные» 8-битный сумматор 8-битная защелка RAM 64 Кбайт × 8 RAM 64 Кбайт × 8 На этой схеме еще чего-то не хватает, но она отображает все 8-битные по- токи данных между различными компонентами. Шестнадцатибитный счетчик Глава 17. Автоматизация 241 предоставляет адреса для двух массивов 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 Коды команд «Сложить» и «Вычесть» отличаются только младшим битом значения, который мы будем называть C 0 . Если значение кода команды равно 21h, то схема должна делать то же самое, что и в случае выполнения команды «Сложить», за исключением того, что данные из массива «Данные» инверти- руются перед попаданием в сумматор, а для входа сумматора CI задается зна- чение 1. Сигнал C 0 может выполнять обе операции в обновленном сумматоре, дополненном инвертором. 242 Код Clk Clr Clk Clr Sel 8 8 8 CI DO 8 Addr DO 16 DI Селектор «2 на 1» 8 Инвертор 8 Addr C 0 W 16-битный счетчик Пульт управления Пульт управления «Код» «Данные» 8-битный сумматор 8-битная защелка RAM 64 Кбайт × 8 RAM 64 Кбайт × 8 Предположим, нам нужно сложить два числа: 56h и 2Ah, а затем из по- лученной суммы вычесть 38h. Это можно сделать, используя следующие коды и данные, хранящиеся в двух массивах RAM. Результат сохраняется здесь 10h 20h 21h FFh 0000h: Сохранить 11h Загрузить Сложить Вычесть Остановить 56h 2Ah 38h 0000h: «Код» «Данные» Глава 17. Автоматизация 243 После выполнения операции «Загрузить» аккумулятор содержит значение 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 А затем старшие (крайние слева). 76h + 23h 99h В результате получится число 99D7h. Если мы сохраним два 16-битных числа в памяти, как показано на рисунке, результат D7h будет сохранен по ад- ресу 0002h, а 99h — по адресу 0005h. 244 Код Старший байт результата 10h 20h 11h 20h 0000h: 10h ABh 2Ch 11h FFh 76h 23h Младший байт результата 0000h: «Код» «Данные» Сохранить Загрузить Сложить Сохранить Загрузить Сложить Остановить Разумеется, это будет работать не всегда. Такой метод подходит для сло- жения чисел, выбранных в качестве примера. Если нам требуется сложить числа 76ABh и 236Ch, при сложении двух младших байтов возникает перенос. ABh + 6Ch 117h Этот перенос должен быть прибавлен к сумме двух старших байтов для получения окончательного результата — 9A17h. 1h + 76h + 23h 9Ah Можем ли мы сделать так, чтобы наш сумматор корректно складывал два 16-битных числа? Да, для этого достаточно сохранить бит переноса, по- лученный от 8-битного сумматора при выполнении первой операции сложе- ния, а затем подать этот бит на вход сумматора для переноса при следующем сложении. Как можно сохранить этот бит? С помощью однобитной защелки (на этот раз назовем ее защелкой для переноса). Использование защелки для переноса требует еще одного кода команды — «Сложить с переносом». При сложении 8-битных чисел используется обычная команда «Сложить». На вход сумматора для переноса (CI) подается значение 0, а значение, возникающее на выходе для переноса (CO), сохраняется в защелке для переноса (хотя в его использовании вообще нет необходимости). При суммировании двух 16-битных чисел используем обычную команду «Сложить» для сложения младших байтов. Вход сумматора CI равен 0, а значение Глава 17. Автоматизация 245 выхода CO сохраняется в защелке для переноса. Для сложения двух старших байтов будем использовать новую команду «Сложить с переносом». В данном случае при сложении двух чисел на вход сумматора CI подается значение, со- храненное в защелке для переноса. Таким образом, если в результате первой операции сложения возник перенос, этот бит переноса используется при вто- ром сложении. Если переноса не возникло, выходное значение защелки для переноса равно 0. Для вычитания одного 16-битного числа из другого потребуется еще одна новая команда — «Вычесть с заимствованием». Как правило, выполнение коман- ды «Вычесть» предполагает инвертирование вычитаемого и подачу на вход сумматора CI значения 1. В этом случае выход для переноса также равен 1, — это нормально, на это явление можно не обращать внимания. Однако при вы- читании 16-битного числа значение этого выхода необходимо сохранить в за- щелке для переноса. При втором вычитании это значение должно быть подано на вход сумматора CI. Учитывая две новые операции, «Сложить с переносом» и «Вычесть с за- имствованием», в общей сложности мы имеем семь кодов команд. Операция Код Загрузить 10h Сохранить 11h Сложить 20h Вычесть 21h Сложить с переносом 22h Вычесть с заимствованием 23h Остановить FFh Число, подаваемое в сумматор, инвертируется при выполнении операции «Вычесть» или «Вычесть с заимствованием». Выходной сигнал сумматора CO подается на вход защелки для переноса. Он сохраняется в защелке всякий раз, когда выполняются операции «Сложить», «Вычесть», «Сложить с переносом» или «Вычесть с заимствованием». Значение входа для переноса 8-битного сум- матора устанавливается в 1 при выполнении операций «Вычесть», или «Сло- жить с переносом», или «Вычесть с заимствованием», когда выход защелки для переноса равен 1. В результате выполнения команды «Сложить с переносом» значение входа сумматора для переноса бывает равно 1 только в том случае, когда при выпол- нении предыдущей команды «Сложить» или «Сложить с переносом» на выходе из сумматора возник перенос. Таким образом, мы используем команду «Сложить 246 Код с переносом» всякий раз, когда складываем многобайтные числа, вне зависи- мости от того, есть ли в этой операции необходимость. Вот как следует зако- дировать продемонстрированную ранее операцию сложения 16-битных чисел. 10h 20h 11h 22h 0000h: 10h ABh 2Ch 11h FFh 76h 23h 0000h: Сохранить Загрузить Сложить с переносом Сохранить Загрузить Сложить Остановить «Код» «Данные» Старший байт результата Младший байт результата Такая последовательность операций позволяет получить правильный от- вет независимо от того, какие цифры мы складываем. Добавление двух новых кодов команд значительно расширило функцио- нал сумматора. Мы больше не ограничиваемся сложением 8-битных значений. Многократное использование команды «Сложить с переносом» позволяет скла- дывать 16-, 24-, 32-, 40-битные значения и т. д. Предположим, нам нужно сло- жить 32-битные числа 7A892BCDh и 65A872FFh. Для этого потребуется лишь одна команда «Сложить» и три команды «Сложить с переносом». 10h 20h 11h 22h 0000h: 10h CDh FFh 11h 10h 2Bh 72h 0000h: Следующий за младшим байт результата 89h 22h A8h 11h 10h 7Ah 22h 65h 11h FFh Следующий за старшим байт результата «Код» «Данные» Сохранить Сложить с переносом Сложить с переносом Сохранить Загрузить Сохранить Загрузить Загрузить Сложить с переносом Сохранить Загрузить Сложить Остановить Старший байт результата Младший байт результата Конечно, вводить эти числа в память не очень удобно. При этом не только приходится использовать переключатели для представления двоичных чисел. Глава 17. Автоматизация 247 Сами числа сохраняются в несмежных ячейках, например 7A892BCDh оказы- вается в ячейках 0000h, 0003h, 0006h и 0009h, начиная с младшего байта. Для получения окончательного результата необходимо проверить значения, хра- нящиеся в ячейках 0002h, 0005h, 0008h и 000Bh. Более того, текущая конструкция нашего сумматора не допускает повтор- ного использования результатов в последующих операциях. Допустим, нужно сложить три 8-битных числа, а затем вычесть из этой суммы другое 8-битное число и сохранить результат. Для этого потребуются команда «Загрузить», две команды «Сложить», команды «Вычесть» и «Сохранить». А если нужно вы- честь из этой исходной суммы другие числа, в то время как сумма эта недо- ступна, поскольку каждый раз нам пришлось бы ее пересчитывать? Проблема в том, что созданный нами сумматор обращается к ячейкам массивов «Код» и «Данные» одновременно и последовательно, начиная с адреса 0000h. Каждая команда в массиве «Код» соответствует ячейке массива «Данные» по тому же адресу. Когда в результате выполнения команды «Сохранить» в массиве «Дан- ные» сохраняется некоторое значение, в дальнейшем оно уже не может быть загружено в аккумулятор. Чтобы решить эту проблему, я намерен внести в сумматор фундамен- тальное и радикальное изменение, которое поначалу может показаться безум- но сложным. Однако со временем (надеюсь) вы оцените ту гибкость, которую оно обеспечивает. Итак, в настоящее время у нас есть семь кодов команд. Операция Код Загрузить 10h Сохранить 11h Сложить 20h Вычесть 21h Сложить с переносом 22h Вычесть с заимствованием 23h Остановить FFh Каждый из этих кодов занимает в памяти один байт. Теперь нужно, что- бы эти коды, за исключением кода команды «Остановить», занимали по три байта. Первый байт будет занят самим кодом, а в следующих двух будет хра- ниться 16-битный адрес ячейки памяти. Для команды «Загрузить» этот адрес указывает местоположение в массиве «Данные», где содержится байт для за- грузки в аккумулятор. Для команд «Сложить», «Вычесть», «Сложить с пере- носом» и «Вычесть с заимствованием» этот адрес указывает местоположение 248 Код байта, который должен быть прибавлен или вычтен из значения, содержаще- гося в аккумуляторе. Для команды «Сохранить» этот адрес указывает место- положение, где нужно сохранить содержимое аккумулятора. Например, про- стейшая задача для текущей версии сумматора — сложение двух чисел. Для этого в массивы «Код» и «Данные» необходимо внести следующие значения. 10h 20h 11h 0000h: FFh 4Ah B5h 0000h: Результат Сохранить Загрузить Сложить Остановить «Код» «Данные» В пересмотренной версии сумматора для хранения каждой команды, кро- ме «Остановить», требуется три байта. 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. Благодаря этому изменению мы можем хранить слагаемые и результат более разумным способом, возможно, в той области памяти, которую еще ни- когда не использовали. Глава 17. Автоматизация 249 76h ABh 4000h: Старший байт результата сохраняется здесь 23h 2Ch Младший байт результата сохраняется здесь 4002h: 4004h: «Данные» Эти шесть ячеек не обязательно должны располагаться последовательно. Они могут быть разбросаны по всему массиву «Данные». Для сложения чисел, находящихся в этих ячейках памяти, в массиве «Код» необходимо сохранить следующие значения. 10h 40h 01h 40h 0000h: Сложить байт из ячейки 4003h со значением в аккумуляторе 20h Загрузить байт из ячейки 4001h в аккумулятор 03h 11h Сохранить содержимое аккумулятора в ячейке 4005h 40h 05h 10h 40h 00h 21h Загрузить байт из ячейки 4000h в аккумулятор Сложить с переносом байт из ячейки 4002h со значением в аккумуляторе 40h 02h 11h Сохранить содержимое аккумулятора в ячейке 4004h 40h 04h FFh Остановить 0003h: 0006h: 0009h: 000Ch: 000Fh: 0012h: «Код» «Код» Обратите внимание: сначала складываются два младших байта, хранящихся в ячейках 4001h и 4003h, а результат сохраняется в ячейке 4005h. Два старших байта (содержащихся в ячейках 4000h и 4002h) складываются с использовани- ем команды «Сложить с переносом», а результат сохраняется в ячейке 4004h. Если удалим команду «Остановить» и добавим новые команды в массив «Код», то при выполнении последующих расчетов сможем использовать исходные слагаемые и их сумму путем простого обращения к соответствующим адресам. Ключевым моментом при реализации этой идеи является подключение выхода массива «Код» DO к трем 8-битным защелкам. В каждой из этих за- щелок хранится один из байтов 3-байтной команды. Первая защелка содержит код команды, вторая — старший байт адреса, третья — младший байт адреса. 250 Код Выход второй и третьей защелок становится 16-битным адресом ячейки в мас- сиве «Данные». Clk Clr RAM 64 Кбайт × 8 RAM 64 Кбайт × 8 Addr DO Пульт управления Пульт управления 16 8 8-битная защелка 8-битная защелка 8-битная защелка 8 8 8 16 8 8 8 Addr DO 8 DI 8 16-битный счетчик Clk W Clk Clk «Код» «Данные» Процесс извлечения команды из памяти называется выборкой команды. Каждая команда в нашем сумматоре занимает три байта, и она извлекается из памяти по одному байту за раз; для извлечения команды требуются три цикла синхросигнала, для всего командного цикла — четыре. Эти изменения, безусловно, усложняют систему управляющих сигналов. Когда сумматор производит серию действий в соответствии с кодом, это называется выполнением команды. Однако это не значит, что машина живая. Она не анализирует машинный код и не решает, что ей делать. Каждый ма- шинный код просто особым образом запускает различные управляющие сиг- налы, заставляя машину выполнять различные действия. Важно: расширение функционала сумматора приводит к замедлению его работы. При той же частоте осциллятора машина складывает числа в четыре раза медленнее по сравнению с первым сумматором, описанным в этой гла- ве. Это проявление инженерного принципа «Бесплатных завтраков не быва- ет» *, смысл которого в том, что улучшение одного аспекта машины приводит к ухудшению другого. * В математическом фольклоре теорема No free lunch означает, что два любых алгоритма оптимизации эквивалентны, если их эффективность сравнивается посредством средних показателей по всем возможным проблемам. Так пояснили в 2005 году Дэвид Волперт и Вильям Макриди, в работе которых (No Free Lunch Theorems for Optimization) это словосочетание появилось впервые (в 1995 году). Сама работа носила более математический и формальный характер, а теоремы, предложенные в ней, были математически доказаны. Прим. науч. ред. Глава 17. Автоматизация 251 Если бы вы на самом деле собирали такой сумматор из реле, то основными компонентами схемы, очевидно, были бы два массива RAM емкостью 64 ки- лобайта. В самом начале вы, вероятно, сэкономили на этих компонентах, ре- шив, что пока можете обойтись одним килобайтом памяти. Если бы вы были уверены, что сохраните все данные в ячейках с 0000h по 03FFh, то могли бы спокойно использовать память емкостью менее 64 килобайта. Впрочем, вы, вероятно, не в восторге от необходимости использовать два массива RAM. На самом деле это необязательно. Я ввел два массива RAM (один с кодами и один с данными) для того, чтобы архитектура сумматора была максимально понятной и простой. Но сейчас, когда мы решили, что каждая команда будет занимать три байта (причем второй и третий байты будут со- держать адрес, где находятся данные), нам не нужны два отдельных массива RAM. Коды команд и данные можно хранить в одном массиве. Чтобы реализовать это, нужен селектор «2 на 1» для определения спосо- ба адресации массива RAM. Как правило, один адрес, как и раньше, подается на вход селектора с 16-битного счетчика. Выход DO массива RAM по-преж- нему подключен к трем защелкам, в которых сохраняются код команды и два байта адреса, сопровождающего каждую команду. Однако 16-битный адрес подается и на второй вход селектора «2 на 1». После сохранения этого адреса в защелках селектор передает его на адресный вход массива RAM. Clk Clr Addr DO Селектор «2 на 1» 16 16 16 8 8 8 8 8 16 8 8 8 8 DI W Clk Clk Clk Sel 16-битный счетчик Пульт управления 8-битная защелка 8-битная защелка 8-битная защелка «Код» «Данные» RAM 64 Кбайт × 8 252 Код Мы значительно продвинулись. Теперь можно ввести команды и данные в один массив RAM. Например, на следующей диаграмме показан процесс сло- жения двух 8-битных чисел и вычитания из полученной суммы третьего числа. 10h 00h 10h 00h 0000h: Сложить байт по адресу 0011h со значением в аккумуляторе 20h Загрузить байт по адресу 0010h в аккумулятор 11h 21h Вычесть байт по адресу 0012h из значения в аккумуляторе 00h 12h 11h 00h 13h FFh Сохранить содержимое аккумулятора по адресу 0013h Остановить 45h A9h 8Eh 000Ch: 0010h: Сохранить результат здесь Как обычно, команды сохраняются начиная с адреса 0000h, поскольку именно с этой ячейки счетчик начинает адресацию массива RAM после обну- ления. Последняя команда «Сохранить» хранится по адресу 000Ch. Мы мог- ли бы сохранить три числа и результаты в любом месте массива RAM (разуме- ется, за исключением первых 13 байт, поскольку они заняты кодами команд), однако решили остановиться на данных, начиная с ячейки по адресу 0010h. Предположим, что нам понадобилось прибавить к результату еще два числа. Можно, конечно, заменить все только что введенные команды новыми, но мы не хотим этого делать. Возможно, мы предпочли бы после выполнения этих ко- манд просто выполнить новые, заменив перед этим код команды «Остановить» кодом новой команды «Загрузить» в ячейке 000Ch. Однако нам требуются две новые команды «Сложить», команда «Сохранить» и новая команда «Остано- вить». Единственная проблема: в ячейке 0010h хранятся данные. Их необхо- димо переместить дальше, изменив при этом ссылающиеся на них команды. Глава 17. Автоматизация 253 Может показаться, что объединение кода и данных в одном массиве RAM не было такой уж хорошей идеей. Уверяю вас, рано или поздно такая пробле- ма обязательно бы возникла. Так что давайте решим ее. В данном случае мож- но попробовать ввести коды новых команд, начиная с адреса 0020h, а новые данные — с адреса 0030h. 10h 00h 13h 00h 0020h: Сложить байт по адресу 0030h со значением в аккумуляторе 20h Загрузить байт по адресу 0013h в аккумулятор 30h 20h Сложить байт по адресу 0031h со значением в аккумуляторе 00h 31h 11h 00h 32h FFh Сохранить содержимое аккумулятора по адресу 0032h Остановить 43h 2Fh 0030h: Сохранить результат здесь Обратите внимание: первая команда «Загрузить» ссылается на ячейку 0013h, в которой хранится результат первого расчета. Итак, с адреса 0000h в памяти хранятся команды, с 0010h — некоторые данные, с 0020h — еще команды, а с адреса 0030h — еще данные. Нам нужно, чтобы сумматор выполнил все команды, начиная с адреса 0000h. Мы знаем, что следует удалить команду «Остановить» из ячейки 000Ch. Под словом «удалить» я подразумеваю ее замену чем-то другим. Достаточно ли этого? Проблема в том, что все, чем мы заменим команду «Остановить», будет интерпретироваться как код команды. Это касается и того, что будет храниться через каждые три ячейки после него — по адресам 000Fh, 0012h, 0015h, 0018h, 001Bh и 001Eh. Что, если одним из этих значений окажется число 11h, которое соответствует команде «Сохранить»? Что, если два байта после кода команды «Сохранить» будут ссылаться на ячейку 0023h? Это заставит сумматор сохра- нить содержимое аккумулятора в этой ячейке. Однако в ней уже содержится 254 Код что-то важное! И даже если ничего подобного не произойдет, после кода коман- ды по адресу 001Eh сумматор извлечет код из ячейки 0021h, а не 0020h, где на самом деле находится код нашей следующей команды. Все ли согласны, что мы не можем просто удалить команду «Остановить» из ячейки 000Ch и надеяться на лучшее? Мы можем заменить ее новой командой под названием «Перейти». Давай- те добавим ее в наш репертуар. |