Глава 19
Два классических микропроцессора
Микропроцессор, представляющий собой совокупность всех компонентов центрального процессорного устройства (ЦПУ), объединенных на одной кремниевой микросхеме, появился в 1971 году. Начало было скромным: первый микропроцессор Intel 4004 содержал около 2300 транзисторов. Сегодня количество транзисторов в микропроцессорах, предназначенных для домашних компьютеров, приблизилось к отметке в десять миллионов.
Тем не менее принцип его работы на фундаментальном уровне остался прежним. Огромное количество дополнительных транзисторов в современных чипах может выполнять интересные функции; на первых этапах изучения оно скорее отвлекает, чем проясняет ситуацию. Для четкого представления работы микропроцессора рассмотрим его первые версии.
Микропроцессоры, о которых пойдет речь, появились в 1974 году. В апреле Intel представила свою микросхему 8080, а в августе Motorola, которая с начала 1950-х годов занималась производством полупроводников и транзисторных устройств, выпустила микросхему 6800. В тот год на рынке появились не только эти микропроцессоры. Тогда же компания Texas Instruments представила свою 4-разрядную микросхему TMS1000, которая устанавливалась во многие калькуляторы, игрушки и другие устройства, а National Semiconductor выпустила первый 16-разрядный микропроцессор PACE. Оглядываясь назад, можно сказать, что микросхемы 8080 и 6800, безусловно, сыграли наиболее значимую роль в истории развития вычислительной техники.
Компания Intel начала продавать микросхему 8080 по цене 360 долларов США, как бы подтрунивая над мейнфреймами System/360 компании IBM, на которые крупные корпорации тратили миллионы долларов. Нельзя сказать, что микросхема 8080 могла тягаться с мейнфреймом System/360, однако в последующие несколько лет IBM не могла обойти вниманием эти миниатюрные компьютеры.
Чип 8080 — это 8-разрядный микропроцессор, который содержит около 6000 транзисторов, работает с тактовой частотой два мегагерца и может адресовать 64 килобайта памяти. Микросхема 6800 содержит около 4000 транзисторов и тоже может адресовать 64 килобайта памяти. Первые чипы 6800 работали на частоте один мегагерц, но к 1977 году Motorola представила их усовершенствованные версии, работавшие с частотой 1,5 и 2 мегагерца.
Микросхемы 8080 и 6800 называются однокристальными микропроцессорами, или однокристальными компьютерами. Процессор — это только один из компонентов компьютера. В дополнение к нему требуются по крайней мере некоторое оперативное запоминающее устройство (ОЗУ), какой-то способ, позволяющий пользователю записать информацию в компьютер (устройство ввода), а также извлечь ее из него (устройство вывода). Кроме того, необходимо еще несколько других микросхем для объединения всех компонентов. Я опишу их подробнее в главе 21.
А сейчас рассмотрим сам микропроцессор. Часто его описание сопровождается блок-схемой, которая иллюстрирует внутренние компоненты микропроцессора и то, как они связаны между собой. Однако на это мы уже насмотрелись в главе 17. Здесь мы постараемся понять, что происходит внутри процессора, наблюдая, как он взаимодействует с внешним миром. Другими словами, мы можем представить микропроцессор в качестве «черного ящика», внутреннюю работу которого нам не обязательно досконально изучать, чтобы разобраться в его функциях. Для этого достаточно ознакомиться с входными и выходными сигналами, в частности набором команд.
Чипы 8080 и 6800 — это интегральные микросхемы с 40 выводами. Чаще всего длина их корпуса составляет пять сантиметров, ширина — около 1,5 сантиметра, а высота — около трех миллиметров.
Разумеется, мы говорим только о корпусе. Размер кремниевой пластины внутри намного меньше: в ранних версиях 8-разрядных микропроцессоров это квадрат со стороной около шести миллиметров. Корпус защищает кремниевый чип и обеспечивает доступ ко всем его входам и выходам. На следующей схеме показаны функции 40 выводов микросхемы 8080.
Каждому из созданных нами электрических или электронных устройств нужен источник питания. Одна из особенностей микросхемы 8080 — ей необходимы три различных напряжения. На контакт 20 должно подаваться напряжение пять вольт, на контакт 11 — минус пять вольт, а на контакт 28 — 12 вольт. Контакт 2 подключается к земле. (В 1976 году Intel выпустила микросхему 8085 с более простой системой электропитания.)
Все остальные контакты изображены в виде стрелок. Стрелка, направленная от микросхемы, обозначает выходной сигнал. Это контролируемый микропроцессором сигнал, на который реагируют другие чипы. Стрелка, направленная к микросхеме, — входной сигнал, поступающий от другого чипа. Некоторые контакты используются в качестве как входов, так и выходов.
Для работы процессора, описанного в главе 17, необходим осциллятор. Микросхеме 8080 требуются два разных сигнала тактовой синхронизации с частотой два мегагерца, обозначенные на схеме символами 01 и 02 рядом с контактами 22 и 15. Эти сигналы удобнее генерировать с помощью другого чипа компании Intel под названием генератор тактовых импульсов 8224. К этому чипу просто подключается кристалл кварца с частотой 18 мегагерц, а все остальное делает генератор.
Микропроцессор всегда предусматривает несколько выходных сигналов для адресации памяти. Количество сигналов, используемых для этой цели, напрямую связано с объемом памяти, к которой может обращаться микропроцессор. Чип 8080 имеет 16 таких сигналов, обозначенных символами от A0 до A15, что позволяет ему адресовать 216, или 65 536 байт.
Микропроцессор 8080 является 8-разрядным, значит, он считывает данные из памяти и записывает их по восемь бит за один раз. Для этого используются контакты с D0 по D7, которые работают как на вход, так и на выход. Когда микропроцессор считывает байт из памяти, эти контакты функционируют как входы; когда микропроцессор записывает байт в память — как выходы.
Еще десять контактов микропроцессора предусмотрены для управляющих сигналов. Например, для сброса микропроцессора используется входной сигнал RESET. Выходной сигнал WR заставляет микропроцессор записать байт в память. (Сигнал WR соответствует входному сигналу записи памяти W.) Кроме того, иногда при считывании микропроцессором команд из памяти на контакты с D0 по D7 подаются другие управляющие сигналы. Для обработки этих дополнительных сигналов компьютеры, построенные на основе микропроцессора 8080, обычно используют системный контроллер 8228. Система управляющих сигналов микропроцессора 8080 печально известна своей сложностью, поэтому, если вы не намерены собирать компьютер на основе этого чипа, лучше не мучайте себя, пытаясь в ней разобраться.
Предположим, что микропроцессор 8080 подключен к памяти объемом 64 килобайта, с которой мы можем обмениваться данными независимо от микропроцессора.
После сброса микропроцессор 8080 считывает байт, хранящийся в ячейке памяти 0000h. Для этого на адресные контакты с A0 по A15 подаются 16 нулей. Байт, который он считывает, должен соответствовать команде процессора 8080, а сам процесс его считывания называется выборкой команды.
В компьютере, который мы собрали в главе 17, все команды, за исключением HLT, занимали в памяти по три байта, один байт содержал код команды, а в двух других хранился адрес. Команды процессора 8080 могут занимать один, два или три байта. Одни команды заставляют процессор 8080 считать байт из определенной ячейки памяти, другие — записать байт в определенную ячейку, третьи — выполнить некие внутренние операции без использования оперативной памяти. После обработки первой команды процессор 8080 обращается ко второй команде, хранящейся в памяти, и т. д. Совокупность этих команд — компьютерная программа, способная выполнять интересные функции.
Когда процессор 8080 работает с максимальной частотой два мегагерца, каждый тактовый цикл длится 500 наносекунд (1 / 2 000 000 циклов в секунду = 0,000000500 секунды). Команды компьютера, описанного в главе 17, выполнялись за четыре тактовых цикла. На исполнение команд для процессора 8080 требуется от 4 до 18 тактовых циклов, то есть от двух до девяти микросекунд (миллионных долей секунды).
Вероятно, чтобы понять, на что способен конкретный микропроцессор, нужно изучить весь набор его команд.
Итоговая модель компьютера из главы 17 выполняла всего 12 команд. Набор 8-разрядного микропроцессора может легко содержать до 256 команд, при этом код каждой команды соответствует конкретному 8-битному значению. (На самом деле команд может быть даже больше, если некоторым из них будет соответствовать 2-байтный код.) Микропроцессор 8080 не заходит так далеко, но его набор включает 244 кода. Это количество может показаться довольно большим, однако в целом процессор 8080 не сильно превосходит компьютер из главы 17. Например, если вам нужно произвести операцию умножения или деления с помощью процессора 8080, все равно придется написать для этого небольшую программу.
Как вы помните из главы 17, каждый код в наборе команд процессора обычно соответствует определенному мнемокоду, и некоторые из них сопровождаются аргументами. Однако эти мнемокоды служат исключительно для удобства при обращении к кодам команд. Процессор считывает только байты; он ничего не знает о соответствующем им тексте. (Для ясности буду несколько вольно обращаться с мнемокодами из документации к процессору Intel 8080.)
Набор компьютера из главы 17 включал две важные команды, которые мы первоначально назвали «Загрузить» и «Сохранить». Каждая из этих команд занимала три байта памяти. Первый байт команды «Загрузить» соответствовал ее коду, а два следующих — 16-битному адресу. Процессор загружал байт, хранящийся по этому адресу, в аккумулятор. Аналогично команда «Сохранить» консервировала содержимое аккумулятора по указанному адресу.
Затем мы обнаружили, что коды этих двух команд можно сократить, используя мнемокоды.
LOD A,[aaaa]
STO [aaaa], A
Здесь A — аккумулятор (место назначения для команды «Загрузить» и источник для команды «Сохранить»), а фрагмент aaaa — 16-битный адрес в памяти, который обычно записывается с помощью четырех шестнадцатеричных цифр.
Восьмиразрядный аккумулятор в процессоре 8080 обозначается буквой A, как и аккумулятор в главе 17. Кроме того, в наборе этого процессора есть две команды, которые делают то же самое, что и команды «Загрузить» и «Сохранить» из главы 17. В процессоре 8080 этим командам соответствуют коды 32h и 3Ah, за каждым из которых следует 16-битный адрес, а мнемокодами для них являются STA (Store Accumulator — сохранить из аккумулятора) и LDA (Load Accumulator — загрузить в аккумулятор).
Код
Команда
32
STA [aaaa], A
3A
LDA A,[aaaa]
В дополнение к аккумулятору процессор 8080 имеет шесть регистров, которые также могут содержать 8-битные значения. Эти регистры похожи на аккумулятор. На самом деле аккумулятор считается регистром особого типа. Подобно аккумулятору, остальные шесть регистров являются защелками; процессор может перемещать байты из памяти в регистры и из регистров обратно в память. В отличие от аккумулятора, регистры не так функциональны. Например, при сложении двух 8-битных чисел результат всегда попадает в аккумулятор, а не в один из регистров.
Шесть дополнительных регистров в процессоре 8080 называются B, C, D, E, H и L. Первым делом люди спрашивают: «Что же случилось с F и G?» — а затем задают второй вопрос: «А как насчет I, J и K?» Ответ заключается в том, что регистры H и L обладают некоторыми особенностями, а их название происходит от слов high и low. Часто 8-битные значения в регистрах H и L обрабатываются вместе в виде 16-битной пары регистров HL, при этом в регистре H содержится старший (high) байт, а в регистре L — младший (low). Это 16-битное значение используется для адресации памяти. Чуть позже мы увидим, как это работает.
Так ли необходимы все эти регистры? Почему мы в них не нуждались, собирая компьютер в главе 17? Теоретически использовать их не обязательно, а практически — весьма удобно. Многие компьютерные программы способны одновременно манипулировать несколькими числами. Это проще всего делать, если числа хранятся не в памяти, а в регистрах микропроцессора. Кроме того, программа работает быстрее: чем реже она обращается к памяти, тем быстрее выполняется.
Для команды процессора 8080 под названием MOV (Move — переместить) предусмотрено 63 кода. Эти коды занимают только один байт и, как правило, перемещают содержимое одного регистра в другой или в тот же самый. Множество команд MOV — следствие использования в микропроцессоре семи регистров (включая аккумулятор).
Вот первые 32 команды MOV. Помните, что место назначения соответствует аргументу слева, а источник — аргументу справа.
Код
Команда
Код
Команда
40
MOV B, B
50
MOV D, B
41
MOV B, C
51
MOV D, C
42
MOV B, D
52
MOV D, D
43
MOV B, E
53
MOV D, E
44
MOV B, H
54
MOV D, H
45
MOV B, L
55
MOV D, L
46
MOV B,[HL]
56
MOV D,[HL]
47
MOV B, A
57
MOV D, A
48
MOV C, B
58
MOV E, B
49
MOV C, C
59
MOV E, C
4A
MOV C, D
5A
MOV E, D
4B
MOV C, E
5B
MOV E, E
4C
MOV C, H
5C
MOV E, H
4D
MOV C, L
5D
MOV E, L
4E
MOV C,[HL]
5E
MOV E,[HL]
4F
MOV C, A
5F
MOV E, A
Как видите, весьма удобные команды. При наличии значения в одном из регистров можно переместить его в другой. Обратите внимание на четыре команды, которые используют пару регистров HL, например на следующую.
MOV B,[HL]
Упомянутая выше команда LDA перемещает байт из памяти в аккумулятор; 16-битный адрес этого байта следует непосредственно за кодом команды LDA. Эта команда MOV перемещает байт из памяти в регистр B. Однако адрес байта, который должен быть загружен в регистр, хранится в паре регистров HL. Как 16-битный адрес оказался в паре регистров HL? Это могло произойти разными способами. Возможно, этот адрес был каким-то образом вычислен.
В общем, обе команды загружают байт из памяти в микропроцессор, но используют два разных метода для адресации памяти. Первый метод называется прямой адресацией, а второй — индексной адресацией.
LDA A,[aaaa]
MOV B,[HL]
Второй набор из 32 команд MOV показывает, что ячейки памяти, адресуемые парой регистров HL, могут являться не только источником, но и местом назначения.
Код
Команда
Код
Команда
40
MOV B, B
50
MOV D, B
60
MOV H, B
70
MOV [HL], B
61
MOV H, C
71
MOV [HL], C
62
MOV H, D
72
MOV [HL], D
63
MOV H, E
73
MOV [HL], E
64
MOV H, H
74
MOV [HL], H
65
MOV H, L
75
MOV [HL], L
66
MOV H,[HL]
76
HLT
67
MOV H, A
77
MOV [HL], A
68
MOV L, B
78
MOV A, B
69
MOV L, C
79
MOV A, C
6A
MOV L, D
7A
MOV A, D
6B
MOV L, E
7B
MOV A, E
6C
MOV L, H
7C
MOV A, H
6D
MOV L, L
7D
MOV A, L
6E
MOV L,[HL]
7E
MOV A,[HL]
6F
MOV L, A
7F
MOV A, A
Некоторые из этих команд, например MOV A, A, не делают ничего полезного. Команды MOV [HL],[HL] вообще не существует. Код, который мог бы ей соответствовать, выделен команде HLT (Halt — остановить).
Более показательный способ анализа команд MOV — рассмотрение битового шаблона их кода. Код команды MOV состоит из восьми битов:
01ннниии,
где буквы ннн соответствуют 3-битному коду места назначения, а иии — 3-битному коду источника. Эти 3-битные коды обозначают следующие регистры.
000 = регистр B
001 = регистр C
010 = регистр D
011 = регистр E
100 = регистр H
101 = регистр L
110 = ячейка памяти по адресу HL
111 = аккумулятор
Команда MOV L, E соответствует коду 01101011, или 6Bh. Вы можете свериться с предыдущей таблицей, чтобы убедиться в этом.
Вероятно, где-то внутри процессора 8080 три бита иии используются в селекторе «8 на 1», а три бита ннн управляют дешифратором «3 на 8», определяющим регистр, где будет зафиксировано значение.
Регистры B и C также можно использовать как 16-битную пару регистров BC, а регистры D и E — как 16-битную пару регистров DE. Если в любой из этих пар регистров содержится адрес ячейки памяти, откуда вы хотите считать или куда хотите записать байт, можете использовать следующие команды.
Код
Команда
Код
Команда
02
STAX [BC], A
0A
LDAX A,[BC]
12
STAX [DE], A
1A
LDAX A,[DE]
Другой тип команды Move называется Move Immediate («Переместить непосредственно») и обозначается мнемокодом MVI. Эта команда состоит из двух байтов. Первый — код команды, второй — байт данных. Этот байт перемещается из памяти в один из регистров или в ячейку памяти, адрес которой содержится в паре регистров HL.
Код
Команда
06
MVI B, xx
0E
MVI C, xx
16
MVI D, xx
1E
MVI E, xx
26
MVI H, xx
2E
MVI L, xx
36
MVI [HL], xx
3E
MVI A, xx
Например, после выполнения команды MVI E,37h в регистре E будет содержаться байт 37h. Этот третий метод обращения к памяти называется непосредственной адресацией.
Набор из 32 кодов команд выполняет четыре основные арифметические операции, с которыми мы познакомились, когда собирали процессор (глава 17). К ним относятся сложение (ADD), сложение с переносом (ADC), вычитание (SUB) и вычитание с заимствованием (SBB). Во всех случаях аккумулятор является одним из двух операндов, а также местом назначения для результата.
Код
Команда
Код
Команда
80
ADD A, B
90
SUB A, B
81
ADD A, C
91
SUB A, C
82
ADD A, D
92
SUB A, D
83
ADD A, E
93
SUB A, E
84
ADD A, H
94
SUB A, H
85
ADD A, L
95
SUB A, L
86
ADD A,[HL]
96
SUB A,[HL]
87
ADD A, A
97
SUB A, A
88
ADC A, B
98
SBB A, B
89
ADC A, C
99
SBB A, C
8A
ADC A, D
9A
SBB A, D
8B
ADC A, E
9B
SBB A, E
8C
ADC A, H
9C
SBB A, H
8D
ADC A, L
9D
SBB A, L
8E
ADC A,[HL]
9E
SBB A,[HL]
8F
ADC A, A
9F
SBB A, A
Предположим, что в аккумуляторе А содержится байт 35h, а в регистре B — байт 22h. После выполнения команды SUB A, B в аккумуляторе будет содержаться байт 13h.
Если в A содержится байт 35h, в регистре H — 10h, в регистре L — 7Ch, в ячейке памяти 107Ch — 4Ah, то при выполнении команды ADD A,[HL] байт в аккумуляторе (35h) прибавляется к байту в ячейке, к которой обращается пара регистров HL (4Ah), а результат (7Fh) сохраняется в аккумуляторе.
Команды ADC и SBB позволяют процессору 8080 складывать и вычитать 16-, 24-, 32-битные числа, а также числа большей разрядности. Предположим, что пары регистров BC и DE содержат 16-битные числа. Вы хотите сложить их и поместить результат в пару регистров BC. Это можно сделать так.
MOV A, C ; младший байт
ADD A, E
MOV C, A
MOV A, B ; старший байт
ADC A, D
MOV B, A
Для сложения используются две команды: ADD — для младшего байта, ADC — для старшего. Любой бит переноса, возникающий в результате первого сложения, участвует во втором сложении. Поскольку прибавлять можно только к значению в аккумуляторе, в этом небольшом фрагменте кода команда MOV используется не менее четырех раз. Команды MOV очень часто встречаются в программном коде для процессора 8080.
Пришло время поговорить о флагах микросхемы 8080. В процессоре из главы 17 использовались флаг переноса и флаг нуля. Микросхема 8080 предусматривает еще три флага: знака, четности и вспомогательного переноса. Все флаги хранятся в 8-битном регистре, который называется словом состояния программы (Program Status Word, PSW). Такие команды, как LDA, STA или MOV, не влияют на эти флаги. Однако команды ADD, SUB, ADC и SBB изменяют флаги следующим образом:
флаг знака устанавливается в 1, если старший бит результата равен 1, то есть если результат отрицательный;
флаг нуля устанавливается в 1, если результат равен 0;
флаг четности устанавливается в 1, если результат четен, то есть выраженный в двоичном формате результат содержит четное количество 1; флаг четности устанавливается в 0, если результат нечетен; флаг четности иногда используется для грубой проверки результата на наличие ошибок; при написании программ для процессора 8080 этот флаг используется редко;
флаг переноса устанавливается в 1, если в результате выполнения команды ADD или ADC возникает бит переноса либо в результате выполнения команд SUB и SBB бит переноса не возникает (такая реализация флага переноса отличается от того, как он был реализован в компьютере из главы 17);
флаг вспомогательного переноса устанавливается в 1, если в результате выполнения команды возникает перенос из младшей тетрады в старшую; этот флаг используется только для команды DAA (Decimal Adjust Accumulator — десятичная коррекция аккумулятора).
На флаг переноса непосредственно влияют две команды.
Код
Команда
Значение
37
STC
Установить флаг переноса в 1
3F
CMC
Дополнить флаг переноса до 1 или инвертировать флаг переноса
В отличие от компьютера из главы 17, который тоже выполнял команды ADD, ADC, SUB и SBB (хотя и не с такой же степенью гибкости), процессор 8080 способен еще и на булевы операции И, ИЛИ и исключающее ИЛИ. За выполнение арифметических и логических операций отвечает арифметико-логическое устройство процессора.
Код
Команда
Код
Команда
A0
AND A, B
B0
OR A, B
A1
AND A, C
B1
OR A, C
A2
AND A, D
B2
OR A, D
A3
AND A, E
B3
OR A, E
A4
AND A, H
B4
OR A, H
A5
AND A, L
B5
OR A, L
A6
AND A,[HL]
B6
OR A,[HL]
A7
AND A, A
B7
OR A, A
A8
XOR A, B
B8
CMP A, B
A9
XOR A, C
B9
CMP A, C
AA
XOR A, D
BA
CMP A, D
AB
XOR A, E
BB
CMP A, E
AC
XOR A, H
BC
CMP A, H
AD
XOR A, L
BD
CMP A, L
AE
XOR A,[HL]
BE
CMP A,[HL]
AF
XOR A, A
BF
CMP A, A
Команды AND, OR и XOR выполняются побитово, то есть отдельно над каждой парой битов. Например, в результате выполнения следующих команд значение в аккумуляторе будет равно 05h.
MVI A,0Fh
MVI B,55h
AND A, B
Если бы последней была команда OR, то результат был бы равен 5Fh; если бы последней была команда XOR — 5Ah.
Команда CMP (Compare — сравнить) аналогична команде SUB, за исключением того, что результат не сохраняется в аккумуляторе. Другими словами, команда CMP выполняет вычитание, а затем удаляет результат. В чем же смысл? Во флагах! Флаги говорят о том, как два сравниваемых байта соотносятся. Рассмотрим, например, следующие команды.
MVI B,25h
CMP A, B
После их выполнения содержимое аккумулятора (А) остается прежним. Если значение в A равно 25h, будет установлен флаг нуля, а если значение в A меньше 25h — флаг переноса.
Для восьми арифметических и логических операций также существуют версии, которые выполняются непосредственно над байтами.
Код
Команда
Код
Команда
C6
ADI A, xx
E6
ANI A, xx
CE
ACI A, xx
EE
XRI A, xx
D6
SUI A, xx
F6
ORI A, xx
DE
SBI A, xx
FE
CPI A, xx
Например, две приведенные выше строки можно заменить следующей.
CPI A,25h
Вот еще две команды для процессора 8080.
Код
Команда
27
DAA
2F
CMA
Команда CMA (Complement Accumulator — дополнить аккумулятор) выполняет дополнение значения в аккумуляторе до 1. Каждый 0 обращается в 1, а 1 — в 0. Если в аккумуляторе содержится значение 01100101, то после исполнения команды CMA в нем будет содержаться значение 10011010. Этого же результата можно достичь и с помощью следующей команды.
XRI A, FFh
Упомянутая выше команда DAA (Decimal Adjust Accumulator — десятичная коррекция аккумулятора), вероятно, является самой сложной в наборе команд процессора 8080. Специально для нее в микропроцессоре предусмотрено небольшое устройство.
DAA помогает программисту выполнять арифметические операции над десятичными числами в кодировке BCD (binary-coded decimal — десятичное в двоичной кодировке), где каждая тетрада может принимать значение только в диапазоне от 0000 до 1001, то есть от 0 до 9 в десятичном выражении. В формате BCD в восьми битах байта могут храниться две десятичные цифры.
Предположим, что в аккумуляторе содержится BCD-значение 27h, которое фактически соответствует десятичному значению 27, а в регистре B содержится BCD-значение 94h. (Обычно шестнадцатеричное значение 27h эквивалентно десятичному значению 39.) В результате выполнения следующих команд в аккумуляторе будет содержаться значение BBh, которое, разумеется, не является BCD-значением, поскольку в кодировке BCD-значение тетрады не может превышать 9.
MVI A,27h
MVI B,94h
ADD A, B
Однако при выполнении команды DAA в аккумулятор помещается значение 21h и устанавливается флаг переноса, поскольку сумма десятичных чисел 27 и 94 равна 121. Эта команда может пригодиться для арифметических операций над числами в кодировке BCD.
Часто возникает необходимость в прибавлении 1 к значению или в вычитании 1 из значения. В программе для выполнения умножения, описанной в главе 17, нужно вычесть из значения 1, и мы делали это, прибавляя значение FFh, которое является дополнением до 2 числа –1. Процессор 8080 предусматривает специальные команды для увеличения на 1 (инкрементирования) и уменьшения на 1 (декрементирования) значения в регистре или в ячейке памяти.
Код
Команда
Код
Команда
04
INR B
05
DCR B
0С
INR C
0D
DCR C
14
INR D
15
DCR D
1С
INR E
1D
DCR E
24
INR H
25
DCR H
2C
INR L
2D
DCR L
34
INR [HL]
35
DCR [HL]
3C
INR A
3D
DCR A
Однобайтовые команды INR и DCR влияют на все флаги, кроме флага переноса.
Набор команд процессора 8080 также включает четыре команды циклического сдвига, которые сдвигают содержимое аккумулятора на один бит влево или вправо.
Код
Команда
Значение
07
RLC
Сдвинуть аккумулятор влево
0F
RRC
Сдвинуть аккумулятор вправо
17
RAL
Сдвинуть аккумулятор влево через бит переноса
1F
RAR
Сдвинуть аккумулятор вправо через бит переноса
Эти команды влияют только на флаг переноса.
Предположим, что аккумулятор содержит значение A7h, или 10100111 в двоичном формате. Команда RLC сдвигает биты влево. Старший бит, выталкиваемый за левую границу разрядной сетки, становится младшим, а также определяет состояние флага переноса. В результате получается значение 01001111, а флаг переноса устанавливается в 1. Команда RRC точно так же сдвигает биты вправо. После выполнения команды RRC значение 10100111 превращается в 11010011, а флаг переноса опять устанавливается в 1.
Команды RAL и RAR работают несколько иначе. При выполнении команды RAL содержимое аккумулятора сдвигается влево, старший бит сохраняется во флаге переноса, а в младший бит записывается предыдущее значение флага переноса. Например, если аккумулятор содержит значение 10100111, а флаг переноса равен 0, то после выполнения команды RAL содержимое аккумулятора меняется на 01001110, а во флаг переноса записывается 1. При тех же начальных условиях после выполнения команды RAR значение аккумулятора аналогично меняется на 01010011, а во флаге переноса сохраняется значение 1.
Команды сдвига удобны при умножении числа на 2 (сдвиг влево) и при делении числа на 2 (сдвиг вправо).
Память, к которой обращается микропроцессор, называется памятью с произвольным доступом потому, что микропроцессор может получить доступ к любой конкретной ячейке, просто предоставив ее адрес. Память RAM скорее напоминает книгу, которую можно открыть на любой странице, чем недельную подшивку газет на микрофильме. Чтобы найти нужную информацию в субботнем выпуске, мы должны просмотреть большую часть газет. Так, для воспроизведения последней песни на кассете мы должны практически полностью перемотать одну из ее сторон. Микрофильм и магнитная лента относятся к запоминающим устройствам не с произвольным, а с последовательным доступом.
Память с произвольным доступом, безусловно, хороша, особенно для микропроцессоров, но иногда удобнее использовать запоминающее устройство, доступ к которому осуществляется непроизвольно и непоследовательно. Допустим, вы работаете в офисе, и сотрудники подходят к вашему столу, чтобы дать задание. Выполнение каждого из них предполагает использование папки с документами. Часто при работе над одним заданием вы обнаруживаете, что не можете продолжать, пока не выполните определенную задачу, используя другую папку. Так что поверх первой папки вы кладете вторую и работаете с ней. Затем вам дают еще одно задание, более приоритетное, чем предыдущее, и вы кладете новую папку поверх двух других. Для выполнения этого вам требуется еще одна папка с документами. И вот на вашем столе уже целая стопка из четырех папок.
Это упорядоченный способ хранения и отслеживания всех выполняемых заданий. Самая верхняя папка всегда соответствует приоритетной задаче. После окончания работы с этой папкой вы переходите к следующей. Когда наконец вы разберетесь с последней папкой на своем столе (с той, с которой начали), сможете отправиться домой.
Технически такая форма хранения данных называется «стек». Строится он снизу вверх, а разбирается сверху вниз. Элементы стека организованы по принципу «последним вошел — первым вышел» (Last In First Out, LIFO). Последний элемент, помещенный в стек, удаляется из него первым. Первый добавленный в стек элемент будет удален из него последним.
Компьютеры также могут использовать стек, но не для хранения заданий, а для хранения чисел, что удобно. Добавление элемента в стек называется вталкиванием (push), а его удаление — выталкиванием (pop).
Предположим, вы пишете программу на языке ассемблера, в которой используются регистры A, B и C. На каком-то этапе программе требуется выполнить еще один небольшой расчет, также предполагающий применение регистров A, B и C. В итоге нужно вернуться к тому, что вы делали раньше, и продолжить использовать регистры A, B и C с теми значениями, которые в них хранились.
Безусловно, вы можете просто сохранить значения регистров A, B и C в других ячейках памяти, а затем загрузить их оттуда обратно. Однако тогда нужно будет следить за содержимым ячеек памяти. Более удобный способ — помещение (вталкивание) значений регистров в стек.
PUSH A
PUSH B
PUSH C
Я объясню, как работают эти команды, чуть позже. Пока достаточно понять, что они каким-то образом сохраняют содержимое регистров в памяти LIFO. После выполнения этих команд ваша программа может спокойно использовать эти регистры для других целей. Чтобы вернуть предыдущие значения, вы просто выталкиваете элементы из стека в обратном порядке.
POP C
POP B
POP A
Помните: последний помещенный в стек элемент удаляется из него в первую очередь. Случайное изменение порядка команд POP приведет к ошибке.
Преимущество стека в том, что его могут использовать разные разделы программы, не вызывая проблем. Например, после помещения в стек значений регистров A, B и C другому разделу программы может понадобиться сделать то же самое с регистрами C, D и E.
PUSH C
PUSH D
PUSH E
Для восстановления значения регистров используются команды РОР.
POP E
POP D
POP C
После их выполнения из стека будут извлечены значения регистров C, B и A.
Как реализуется стек? Прежде всего, это просто раздел памяти, не используемый для хранения каких-либо других данных. Для обращения к этому разделу памяти микропроцессор 8080 предусматривает специальный 16-битный регистр, который называется указателем стека (Stack Pointer, SP).
Приведенные выше примеры добавления и удаления элементов из стека не вполне точно демонстрируют работу микропроцессора 8080. Команда 8080 PUSH фактически сохраняет в стеке 16-битные значения, а команда POP извлекает их. Именно поэтому вместо таких команд, как PUSH C и POP C, используем следующие восемь.
Код
Команда
Код
Команда
C5
PUSH BC
C1
POP BC
D5
PUSH DE
D1
POP DE
E5
PUSH HL
E1
POP HL
F5
PUSH PSW
F1
POP PSW
Команда PUSH BC сохраняет в стеке значения регистров B и C, а команда POP BC извлекает их. Аббревиатура PSW в последней строке означает слова состояния программы, которые, как вы помните, представляют собой 8-битный регистр, содержащий флаги. Две команды в нижней строке фактически помещают и извлекают из стека содержимое как аккумулятора, так и регистра PSW. Если вы хотите сохранить содержимое всех регистров и значения всех флагов, используйте следующие команды.
PUSH PSW
PUSH BC
PUSH DE
PUSH HL
Когда вам потребуется восстановить содержимое этих регистров, обращайтесь к командам POP в обратном порядке.
POP HL
POP DE
POP BC
POP PSW
Как работает стек? Предположим, что указатель стека равен 8000h. При выполнении команды PUSH BC происходит следующее:
значение указателя стека уменьшается на 1 и становится равным 7FFFh;
содержимое регистра B сохраняется по адресу, соответствующему значению указателя стека, то есть в ячейке 7FFFh;
значение указателя стека уменьшается на 1 и становится равным 7FFEh;
содержимое регистра C сохраняется по адресу, соответствующему значению указателя стека, то есть в ячейке 7FFEh.
Команда POP BC, выполняемая при значении указателя стека, все еще равном 7FFEh, производит обратные операции:
содержимое регистра C загружается из ячейки, адрес которой соответствует значению указателя стека, то есть из ячейки 7FFEh;
значение указателя стека увеличивается на 1 и становится равным 7FFFh;
содержимое регистра B загружается из ячейки, адрес которой соответствует значению указателя стека, то есть из ячейки 7FFFh;
значение указателя стека увеличивается на 1 и становится равным 8000h.
Каждая команда PUSH увеличивает размер стека на два байта. Существует вероятность того, что из-за ошибки в программе размер стека станет настолько большим, что его содержимое начнет сохраняться в ячейках, занятых необходимым программе кодом или данными. Эта проблема называется переполнением стека. Точно так же слишком большое количество команд POP может привести к преждевременному исчерпанию стека.
Если к процессору 8080 подключена память объемом 64 килобайт, имеет смысл установить начальное значение указателя стека равным 0000h. Первая команда PUSH уменьшает это значение на 1 — до FFFFh. После этого стек займет область памяти с самыми высокими адресами, которая максимально удалена от ваших программ, хранящихся, вероятно, начиная с 0000h.
Установить значение указателя стека можно с помощью команды LXI (Load Extended Immediate — расширенная непосредственная загрузка). Перечисленные далее команды также загружают в 16-битные пары регистров два байта, которые следуют за кодом команды.
Код
Команда
01
LXI BC, xxxx
11
LXI DE, xxxx
21
LXI HL, xxxx
31
LXI SP, xxxx
Команда LXI BC,527Ah эквивалентна следующим командам.
MVI B,52
MVI C,7Ah
Однако команда LXI позволяет сэкономить один байт. Кроме того, последняя команда LXI в предыдущей таблице используется для установки конкретного значения для указателя стека. Часто эта команда одной из первых выполняется микропроцессором после его перезапуска.
0000h: LXI SP,0000h
Увеличить и уменьшить на 1 значение пары регистров и указателя стека можно с помощью следующих команд.
Код
Команда
Код
Команда
03
INX BC
0B
DCX BC
13
INX DE
1B
DCX DE
23
INX HL
2B
DCX HL
33
INX SP
3B
DCX SP
Рассмотрим еще несколько 16-битных команд. Следующие команды складывают содержимое 16-битных пар регистров с содержимым пары регистров HL.
Код
Команда
09
DAD HL, BC
19
DAD HL, DE
29
DAD HL, HL
39
DAD HL, SP
Эти команды позволяют сэкономить несколько байтов. Например, первая из них, как правило, требует шесть байт.
MOV A, L
ADD A, C
MOV L, A
MOV A, H
ADC A, B
MOV H, A
Команда DAD обычно используется для вычисления адресов ячеек памяти и влияет только на флаг переноса.
Следующие два кода команд сопровождаются 2-байтовым адресом ячейки памяти и позволяют сохранить содержимое пары регистров HL в соответствующей ячейке, а также загрузить из нее содержимое в пару регистров HL.
Код
Команда
Значение
2h
SHLD [aaaa], HL
Записать число из HL в PC
2Ah
LHLD HL,[aaaa]
Загрузить данные в HL
Содержимое регистра L сохраняется по адресу aaaa, а содержимое регистра H — по адресу aaaa + 1.
Эти две команды загружают в счетчик команд (PC) или в указатель стека (SP) значение из пары регистров HL.
Код
Команда
Значение
E9h
PCHL PC, HL
Загрузить значение HL в PC
F9h
SPHL SP, HL
Загрузить значение HL в SP
Команда PCHL — своеобразная команда перехода. После нее процессор 8080 выполняет команду, код которой занимает ячейку по адресу, записанному в паре регистров HL. Команда SPHL — еще один способ установки значения указателя стека.
Следующие две команды позволяют поменять местами содержимое регистров HL с двумя байтами, являющимися «верхними» элементами стека, или с содержимым пары регистров DE.
Код
Команда
Значение
E3h
XTHL HL,[SP]
Поменять местами «верхний» элемент стека и HL
EBh
XCHG HL, DE
Поменять местами DE и HL
Из всех команд перехода для процессора 8080 пока я описал только PCHL. Как вы помните из главы 17, процессор предусматривает регистр под названием «счетчик команд», содержащий адрес ячейки памяти, из которой процессор извлекает следующую команду, подлежащую выполнению. Как правило, счетчик команд заставляет процессор выполнять команды, сохраненные в памяти, последовательно. Однако так называемые команды перехода, или ветвления, позволяют процессору отклониться от этого главного курса. Эти команды загружают в счетчик команд другое значение, поэтому следующая команда извлекается процессором из какой-то другой области памяти.
Несмотря на удобство обычной команды перехода, команды условного перехода более удобные, поскольку заставляют процессор переходить к другому адресу, основываясь на значении определенного флага, например флага переноса или флага нуля. Именно реализация условного перехода превратила автоматизированный сумматор из главы 17 в универсальный цифровой компьютер.
В процессоре 8080 имеется пять флагов, четыре из которых используются для реализации условных переходов. Набор команд 8080 содержит девять команд безусловных и условных переходов, зависящих от того, чему равны флаги нуля, переноса, четности и знака: 1 или 0.
Прежде чем продемонстрировать эти команды, хочу познакомить вас с двумя другими типами команд, имеющих отношение к переходу. Первая — CALL (вызов), она аналогична команде перехода, за исключением того, что перед загрузкой нового адреса в счетчик команд процессор сохраняет предыдущий адрес. Где он сохраняет этот адрес? Разумеется, в стеке!
Эта стратегия подразумевает, что команда вызова сохраняет информацию о том месте, откуда был совершен переход. Сохраненный адрес позволяет процессору вернуться в исходное местоположение. Команда для совершения обратного перехода называется RET (Return — вернуться). Она удаляет из стека 2-байтное значение и загружает его в счетчик команд.
Команды CALL и RET — чрезвычайно важные функции любого процессора, позволяющие программисту реализовывать подпрограммы, которые являются часто используемыми фрагментами кода. (Под словом «часто» обычно я имею в виду «более одного раза».) Подпрограммы — основные организующие элементы программ на языке ассемблера.
Обратимся к примеру. В процессе написания программы на языке ассемблера у вас возникает необходимость в перемножении двух байтов. Вы пишете код для выполнения этой операции, а затем продолжаете работу с программой. На каком-то этапе вам снова требуется перемножить два байта. Поскольку вы уже знаете, как это сделать, можно просто использовать те же команды снова и снова. Собираетесь ли вы во второй раз ввести эти команды в память? Надеюсь, что нет, поскольку это пустая трата времени и памяти. Вместо этого вам следует просто перейти к предыдущему фрагменту кода. Правда, в данном случае обычная команда перехода не сработает, поскольку она не позволяет вернуться к тому месту, с которого был совершен переход. Именно в этом случае пригодятся команды CALL и RET.
Набор команд, позволяющих перемножить два байта, идеально подходит на роль подпрограммы. Давайте рассмотрим одну из них. В главе 17 подлежащие перемножению байты и произведение хранились в определенных ячейках памяти. Приведенная далее подпрограмма 8080 умножает байт в регистре B на байт в регистре C и помещает 16-битное произведение в регистр HL.
Обратите внимание: первая строка подпрограммы начинается с метки Multiply. Эта метка соответствует адресу ячейки памяти, в которой расположена подпрограмма. Подпрограмма начинается с двух команд PUSH. Как правило, она пытается сохранить (а в дальнейшем восстановить) значения любых регистров, которые могут ей потребоваться.
Затем подпрограмма записывает значение 0 в регистры H и L. Для этого вместо команды SUB можно было бы использовать команду MVI (Move Immediate — переместить непосредственно), однако в этом случае потребовались бы четыре команды, а не две. После выполнения подпрограммы в паре регистров HL будет содержаться произведение.
После этого подпрограмма перемещает содержимое регистра B (множитель) в A и проверяет, не равно ли оно 0. Если оно равно 0, подпрограмма завершается, так как произведение — 0. Поскольку значения в регистрах H и L уже равны 0, подпрограмма может просто использовать команду JZ (Jump If Zero — перейти, если ноль), чтобы перейти к двум командам POP в конце программы.
В противном случае подпрограмма записывает в регистр B значение 0. Теперь в паре регистров BC содержится 16-битное множимое, а в аккумуляторе (А) — множитель. Команда DAD прибавляет значение BC (множимое) к значению HL (произведение). Значение множителя в A уменьшается на 1. Пока он не станет равен 0, выполнение команды JNZ (Jump If Not Zero — перейти, если не ноль) будет приводить к повторному сложению значения BC со значением HL. Этот небольшой цикл будет выполняться до тех пор, пока количество операций сложения BC и HL не станет равным множителю. (Более эффективную подпрограмму для умножения можно написать, используя команды сдвига из набора команд процессора 8080.)
Эту подпрограмму для перемножения чисел, например 25h и 12h, можно использовать в программе, добавив следующий фрагмент кода.
MVI B,25h
MVI C,12h
CALL Multiply
Команда CALL сохраняет в стеке значение счетчика команд, которое представляет адрес команды, следующей после CALL. Затем CALL вызывает переход к команде, на которую указывает метка Multiply. Это начало подпрограммы. После того как подпрограмма рассчитает произведение, она выполнит команду RET, в результате чего в счетчик команд будет возвращено значение из стека. Затем будет выполнена команда, следующая после CALL.
Набор команд процессора 8080 предусматривает условные команды вызова и возврата, однако они используются реже, чем обычные команды перехода. Все они перечислены в следующей таблице.
Как вы знаете, к микропроцессору подключается не только память. Компьютерная система обычно требует устройства ввода и вывода (I/O), которые облегчают пользователям взаимодействие с машиной. К этим устройствам, как правило, относятся клавиатура и дисплей.
Как микропроцессор взаимодействует с этими периферийными устройствами? (Все подключенные к микропроцессору компоненты, кроме памяти, называются периферийными.) Конструкция периферийных устройств, подобно памяти, предусматривает интерфейс. Микропроцессор может записывать и считывать данные с периферийного устройства, указывая определенные адреса, на которые оно реагирует. В некоторых микропроцессорах периферийные устройства фактически задействуют адреса, обычно используемые для обращения к памяти. Такая конфигурация называется вводом-выводом с распределением памяти. Тем не менее в процессоре 8080, кроме обычных 65 536 адресов для устройств ввода и вывода, специально зарезервированы 256 дополнительных, которые называются портами ввода/вывода. Адресные сигналы устройств ввода/вывода подаются на входы с A0 по A7, а от обращений к памяти их отличают сигналы, фиксируемые чипом системного контроллера 8228.
Команда OUT записывает содержимое аккумулятора в порт, адресуемый следующим за командой байтом. Команда IN позволяет считать байт в аккумулятор.
Код
Команда
D3
OUT pp
DB
IN pp
Периферийным устройствам иногда требуется привлечь внимание микропроцессора. Например, когда вы нажимаете клавишу на клавиатуре, желательно, чтобы микропроцессор узнавал об этом сразу. Это реализуется благодаря механизму прерываний — сигналам, поступающим от периферийного устройства на вход INT процессора 8080.
Однако после перезапуска микропроцессор 8080 не реагирует на прерывания. Для разрешения прерываний программа должна выполнить команду EI (Enable Interrupts — разрешить прерывания), а для их запрещения — команду DI (Disable Interrupts — запретить прерывания).
Код
Команда
F3
DI
FB
EI
Выходной сигнал процессора 8080 INTE означает, что прерывания были разрешены. Когда у периферийного устройства возникает необходимость прервать работу микропроцессора, оно подает на вход INT сигнал, равный 1. В ответ на него процессор 8080 извлекает из памяти команду, однако управляющие сигналы сообщают о прерывании. В ответ на это периферийное устройство передает процессору 8080 одну из следующих команд.
Код
Команда
Код
Команда
C7
RST 0
E7
RST 4
CF
RST 1
EF
RST 5
D7
RST 2
F7
RST 6
DF
RST 3
FF
RST 7
Эти команды RST (Restart — перезагрузка) аналогичны командам CALL в плане того, что текущее значение счетчика команд сохраняется в стеке. Однако после этого RST осуществляет переход к определенным адресам памяти: RST 0 переходит к ячейке 0000h, RST 1 — к ячейке 0008h и так далее вплоть до RST 7, которая совершает переход к ячейке 0038h. В этих ячейках должны содержаться фрагменты кода, предусмотренного для обработки прерывания. Например, прерывание от клавиатуры привело к выполнению команды RST 4. Это значит, что начиная с ячейки 0020h в памяти должен храниться некоторый код для считывания байта, введенного с клавиатуры. (В главе 21 я объясню все это.)
К настоящему моменту мной описаны 243 кода команд. Существует 12 байтов, которые не соответствуют никаким командам: 08h, 10h, 18h, 20h, 28h, 30h, 38h, CBh, D9h, DDh, EDh и FDh. В сумме это дает 255. Но есть еще один код, о котором я должен упомянуть.
Код
Команда
00
NOP
Команда NOP (No Operation — нет операции) заставляет процессор бездействовать. Для чего она нужна? Для заполнения адресного пространства. Как правило, процессор 8080 может выполнять множество команд NOP без каких-либо неприятных последствий.
Не буду подробно описывать устройство микросхемы 6800 компании Motorola, поскольку ее конструкция и функционал во многом аналогичны соответствующим аспектам процессора 8080. На следующей схеме изображены ее 40 контактов.
Контакт VSS подключается к земле, VCC — к источнику питания с напряжением пять вольт. Как и процессор 8080, микросхема 6800 предусматривает 16 выходных адресных сигналов и восемь сигналов для данных, работающих как на ввод, так и на вывод. Есть сигналы RESET и R/W (чтение/запись). На вход IRQ подается сигнал запроса на прерывание (interrupt request). Считается, что система синхронизации в микросхеме 6800 устроена гораздо проще, чем в процессоре 8080. Чего в микросхеме 6800 нет, так это понятия портов ввода/вывода. Адреса всех устройств ввода и вывода принадлежат общему адресному пространству 6800.
Микросхема 6800 предусматривает 16-битный счетчик команд, 16-битный указатель стека, 8-битный регистр состояния (для флагов) и два 8-битных аккумулятора, называемых A и B.
В качестве аккумулятора, а не простого регистра рассматривается В, поскольку его можно использовать так же, как и A. Однако дополнительных 8-битных регистров в такой микросхеме нет.
Вместо этого 6800 предполагает 16-битный индексный регистр, который может использоваться для хранения 16-битного адреса, подобно паре регистров HL в 8080. Для многих команд адрес может вычисляться путем суммирования значения их индексного регистра и байта, который следует за кодом команды.
Несмотря на то что микросхема 6800 выполняет примерно те же операции, что и процессор 8080 (загрузка, сохранение, сложение, вычитание, сдвиг, переход, вызов), коды команд и соответствующие им мнемокоды у этих чипов совершенно разные. В следующей таблице, например, перечислены команды перехода из набора микросхемы 6800.
Код
Команда
Значение
20h
BRA
Переход
22h
BHI
Переход, если больше
23h
BLS
Переход, если меньше или равно
24h
BCC
Переход, если переноса нет
25h
BCS
Переход, если перенос есть
26h
BNE
Переход, если не равно
27h
BEQ
Переход, если равно
28h
BVC
Переход, если нет переполнения
29h
BVS
Переход, если есть переполнение
2Ah
BPL
Переход, если плюс
2Bh
BMI
Переход, если минус
2Ch
BGE
Переход, если больше или равно 0
2Dh
BLT
Переход, если меньше 0
2Eh
BGT
Переход, если больше 0
2Fh
BLE
Переход, если меньше или равно 0
В микросхеме 6800 не используется флаг четности, однако в отличие от 8080 в ней предусмотрен флаг переполнения. Некоторые из перечисленных команд перехода зависят от комбинаций флагов.
Разумеется, наборы команд 8080 и 6800 различаются. Эти два процессора были спроектированы примерно в одно и то же время двумя разными группами инженеров в двух разных компаниях. Эта несовместимость означает, что ни один из этих процессоров не может выполнить машинный код, написанный для другого. Кроме того, программа, написанная на языке ассемблера одного процессора, не может быть преобразована в коды команд другого. О написании компьютерных программ, работающих на нескольких процессорах, мы поговорим в главе 24.
Существует еще одно интересное различие между 8080 и 6800: в обоих микропроцессорах команда LDA загружает в аккумулятор значение из указанной ячейки памяти. В 8080, например, следующая последовательность байтов загружает в аккумулятор байт из ячейки 347Bh.
Теперь сравните эту последовательность с командой LDA для процессора 6800, использующей так называемый расширенный режим адресации.
Эта последовательность байтов загружает в аккумулятор A байт из ячейки 7B34h.
Различие не сразу бросается в глаза. Конечно, вы ожидали, что коды команд будут разными: 3Ah для 8080 и B6h для 6800. Однако эти два микропроцессора также по-разному обрабатывают адрес, который следует за кодом операции. Процессор 8080 предполагает, что первым должен идти младший байт, за которым следует старший, а процессор 6800 — что первым должен идти старший байт.
Это принципиальное различие между микросхемами Intel и Motorola по части хранения многобайтных значений так и не было преодолено. Микропроцессоры Intel по сей день продолжают сохранять многобайтные значения, начиная с младшего байта, а микропроцессоры Motorola — начиная со старшего байта.
Эти два порядка известны как «от младшего к старшему» (little-endian, способ Intel) и «от старшего к младшему» (big-endian, способ Motorola). Прежде чем спорить, какой из методов лучше, имейте в виду, что термины Big-Endian («тупоконечник») и Little-Endian («остроконечник») взяты из книги Джонатана Свифта «Путешествия Гулливера» и связаны с войной между Лилипутией и Блефуску, разгоревшейся из-за разногласий относительно того, с какого конца следует разбивать вареное яйцо: с острого или с тупого. Этот спор не имеет смысла. (С другой стороны, признаюсь, что подход, который я использовал в компьютере из главы 17, не кажется мне предпочтительным!) Хотя ни один из приведенных методов не может считаться правильным, разница между ними создает дополнительную проблему несовместимости при обмене информацией между системами, основанными на этих различающихся принципах.
Что же стало с двумя классическими микропроцессорами? Процессор 8080 был положен в основу того, что некоторые люди называли первым персональным компьютером, хотя его правильнее было бы назвать первым домашним компьютером. Это был «Альтаир 8800» (Altair 8800), фотография которого украсила обложку журнала Popular Electronics в январе 1975 года.
Если вы внимательно рассмотрите этот компьютер, то заметите на передней панели уже знакомые индикаторы и переключатели. Это тот же примитивный «пульт управления», который я предложил для массива RAM в главе 16.
Вслед за процессором 8080 были выпущены микросхемы Intel 8085 и Z-80 компании Zilog, конкурента Intel, основанного ее бывшим сотрудником Федерико Фаджином, который сделал существенный вклад в разработку микросхемы 4004. Процессор Z-80 был полностью совместим с 8080, но предусматривал множество дополнительных полезных команд. В 1977 году чип Z-80 был использован в микрокомпьютере компании Radio Shack TRS-80 Model 1.
Кроме того, в 1977 году компания Apple Computer Company, основанная Стивом Джобсом и Стивом Возняком, представила компьютер Apple II, в котором вместо 8080 и 6800 использовался малобюджетный чип 6502 компании MOS Technology — усовершенствованная версия микросхемы 6800.
В июне 1978 года компания Intel выпустила 16-битный микропроцессор 8086, способный адресовать один мегабайт памяти. Набор команд 8086 не был совместим с процессором 8080, однако он предусматривал команды для умножения и деления. Год спустя Intel представила микропроцессор 8088, внутренне идентичный чипу 8086, но с побайтной адресацией внешней памяти, позволявшей микропроцессору использовать распространенные в то время 8-битные вспомогательные чипы, разработанные для 8080. Компания IBM применяла микропроцессор 8088 в своем персональном компьютере 5150, представленном осенью 1981 года под названием IBM PC.
Выход IBM на рынок персональных компьютеров серьезно на него повлиял, и многие компании начали выпускать устройства, совместимые с IBM PC. (Тема совместимости будет рассмотрена в последующих главах.) На протяжении многих лет выражение «IBM PC-совместимый» подразумевало использование в устройстве микросхемы Intel, в частности микропроцессора Intel семейства x86. В 1982 году семейство x86 пополнилось чипами 186 и 286, в 1985 году — 32-битным чипом 386, в 1989-м — чипом 486, а в 1993-м — микропроцессорами Intel Pentium, которые в настоящее время устанавливаются в компьютеры, совместимые с IBM PC. Несмотря на то что наборы команд микропроцессоров Intel постоянно расширяются, они продолжают поддерживать коды команд более ранних процессоров, начиная с 8086.
В компьютере Apple Macintosh, впервые представленном в 1984 году, использовался 16-битный микропроцессор Motorola 68000, который является прямым потомком чипа 6800. Процессор 68000 и его более поздние версии (часто объединяемые в серию 68K) — один из самых популярных микропроцессоров.
Начиная с 1994 года в компьютерах Macintosh установлен микропроцессор PowerPC, разработанный совместно компаниями Motorola, IBM и Apple. В его основе лежит микропроцессорная архитектура RISC (Reduced Instruction Set Computing — вычисления с сокращенным набором команд), в рамках которой реализуется попытка увеличения скорости процессора за счет упрощения некоторых его аспектов. На компьютере с архитектурой RISC каждая команда, как правило, имеет одинаковую длину (32 бита в случае PowerPC), доступ к памяти ограничен только командами для загрузки и сохранения, а сами команды выполняют скорее простые операции, нежели сложные. Процессоры на основе архитектуры RISC обычно предусматривают множество регистров, чтобы как можно реже обращаться к памяти.
Микропроцессор PowerPC не может выполнять код, написанный для чипов серии 68K, поскольку имеет совершенно другой набор команд. Однако микропроцессоры PowerPC, которые в настоящее время используются в компьютерах Apple Macintosh, могут эмулировать процессор 68K. Программа-эмулятор, работающая на PowerPC, последовательно анализирует каждый код команды в программе, написанной для чипа серии 68K, и выполняет соответствующее действие. Это происходит не так быстро, как выполнение «родного» кода PowerPC, но это работает.
Согласно закону Мура, количество транзисторов в микропроцессорах должно удваиваться каждые 18 месяцев. Для чего нужны эти многочисленные дополнительные транзисторы?
Некоторые из транзисторов позволили увеличить разрядность процессора. Использование других было обусловлено появлением новых команд. Большая часть современных микропроцессоров предусматривает команды для выполнения операций над числами с плавающей точкой (о чем я расскажу в главе 23). Кроме того, в набор микропроцессоров были добавлены новые команды для произведения некоторых часто повторяющихся вычислений, необходимых для отображения на экранах компьютеров изображений или фильмов.
Для увеличения быстродействия в современных процессорах применяется несколько методов. Один из них называется конвейеризацией. При выполнении одной команды процессор считывает следующую, при этом он до определенной степени предугадывает, как команды перехода могут изменить программный поток. Современные процессоры также включают кеш (cache) — массив сверхскоростной оперативной памяти внутри процессора, в котором хранятся недавно выполненные команды. В компьютерных программах часто используются небольшие циклы, а кеш позволяет обойтись без повторной загрузки входящих в него команд. Все эти функции, повышающие быстродействие процессора, требуют дополнительных логических схем, следовательно, дополнительных транзисторов.
Как я уже говорил, микропроцессор — это только одна (пусть и самая важная) часть компьютерной системы. Сконструируем такую систему в главе 21, но сначала мы должны научиться записывать в память что-то, кроме кодов команд и чисел. Нам предстоит вернуться в первый класс и заново научиться читать и писать текст.