Аннотация: приложение к главе 7 книги о Фокале: пока только про систему команд PDP-11 и MSP-430 (ред.2.1 от 13.11.16 - добавил: расширенный режим МСП-430)
======================= приложение к главе 7 =======================
Там я написал что мол надо бы рассмотреть конкретные системы команд,
да больно далеко это нас уведёт... Ну так рассмотрим их здесь.
Начнём пожалуй с многократно упоминавшейся PDP-11. Типичная мини-ЭВМ
третьего поколения. У нас в своё время было множество машин с этой системой
команд и архитектурой.
Как это выглядело.
Линия СМ-ЭВМ: Первые две СМ-1 и СМ-2 имели другую, свою собственную
систему команд и к семейству PDP-11 не относились. А вот начиная с СМ-3...
- СМ-3, СМ-1300 - младшие модели (с разницей лет в пять-десять);
- СМ-4, СМ-1420 - более старшие, отличающиеся наличием "расширенных" команд
(а у СМ-1420 и сопроцессора для чисел с плавающей запятой) а так же диспетчера
памяти;
- СМ-1410 и СМ-1600 - двухпроцессорные комплексы, второй процессор которых -
для совместимости с машинами серии Мир и М5000.
Модели первой очереди (СМ-3, СМ-4) сделаны на микросхемах мелкой и
средней степени интеграции, второй - с использованием БИС (микропроцессорных
комплектов). Внешне всё это выглядит как ряд узких шкафов ("стоек") с
дверцами спереди и сзади, шириной и глубиной сантиметров по 60 - 70, в которые
вставлены блоки устройств, входящих в машину. В т.ч. центральный процессор
(ЦП), дисководы, накопители на магнитной ленте, мультиплексор для подключения
терминалов, и.т.п. Они все последовательно соединяются "общей шиной" в виде
толстенного кабеля (или даже двух). Плюс отдельно стоящее оборудование, типа
принтера или например дисководов для пакетных дисков, габаритами чуть
поменьше письменного стола, но на колёсиках. Всё это занимало отдельную
комнатку, как правило длинную и узкую, отгороженную стенкой с окном от
помещения по-больше, где размещался "терминал-класс" - штук пятнадцать
алфавитно-цифровых дисплеев. Стены и потолок в обоих помещениях отделывались
дырчатым шумопоглощающим покрытием; сооружался фальш-пол, под которым
проходили кабеля; а в окна вставлялись кондиционеры бакинского производства.
По которым с одного взгляда на фасад здания было видно где именно находится
вычислительный центр.
Линия Электроника: Самая младшая (да и самая старая тоже) модель -
Электроника-100/16. Не путать с Электроникой-100. Обе сделаны к столетнему
юбилею В.И.Ленина и выглядят очень похоже, но 12-и разрядная Э-100 - аналог
не PDP-11, а PDP-8 - другой разработки той же самой фирмы DEC. Эта тоже
весьма интересная одноадресная мини-ЭВМ, имеющая всего 8 (восемь!) команд и
четыре килослова ОЗУ, на фоне монстров предыдущего поколения выглядела
подлинным шедевром минимализма. Между тем на ней решались довольно серьёзные
даже по нынешним временам задачи. (Например расчет голографической
"мультилинзы" - объектива, дающего много изображений одного объекта.) Но в
силу малого количества ресурсов программировалась она исключительно на
ассемблере, а из всех языков высокого уровня "тянула" только интерпретатор
Фокала. Э-100/16 при том же самом объёме памяти была шестнадцатиразрядная...
Как впрочем и все машины семейства PDP-11.
Э-100/16 представляла из себя несколько металлических столов (не очень
удобных), на одном из которых стоял центральный процессор; на другом -
терминал в виде чешской электрической пишущей машинки "Консул"; а на третьем -
"перфостанция", включающая перфоратор и перфосчитыватель. Больше никаких
внешних устройств у того экземпляра, который я наблюдал, небыло. Но
теоретически можно было подключить - в недрах столов были упрятаны
соответствующие интерфейсы. (Зато памяти у неё было в два раза больше -
использовался "ферритовый куб" второго, несколько поломатого процессора.)
Процессор, более всего похожий по внешнему виду на старинную радиолу (но
габаритами ближе к цветному ламповому телевизору) имел стеклянную переднюю
панель, на которой располагался ряд лампочек и под ним ряд клавишей -
"клавишный регистр" - с помощью которых можно лазить непосредственно в
оперативную память и регистры процессора. (Да, это были не кнопки и не тумблеры,
а такие своеобразные переключатели, широкие и плоские, выглядевшие как
горизонтальный флажок, коий можно было поднять вверх, что означало логическую
единицу, или опустить вниз - логический ноль.) Справа несколько управляющих
клавишей: занести адрес, набранный на клавишах клавишного регистра (на шину
адреса, надо понимать); чтение по этому адресу - битики прочитанного слова
отображаются зажиганием лампочек (если "1"); запись слова, набранного на
клавишном регистре по ранее занесённому адресу. А кроме того "пуск программы" и
очень важная клавиша "работа". Если она выключена, то при нажатии на клавишу
"пуск" процессор выполнит ровно одну команду и сразу-же остановится. (Что
позволяет отлаживать программу, написанную в кодах, выполняя её по шагам.)
Справа над управляющими клавишами прямо на стекле была написана программка
"начальный загрузчик". Всё программное обеспечение было на перфолентах (каждая
в отдельной круглой коробочке); грузилось оно в ОЗУ с помощью специальной
программы "абсолютный загрузчик", а она - вот с помощью этого начального,
который предполагалось заносить в память вручную - с помощью клавишного
регистра. Но далеко не каждый раз, а только если вдруг испортится: память была
на ферритовых колечках - энергонезависимая. (Как устроена - см. в приложении.)
Особенность такой памяти: нельзя было писать (а равно и читать) много раз
подряд одно и то же слово - колечки (диаметром 0.8 мм) перегревались и могли
натурально расколоться. Говорят, какие-то гады использовали это свойство
ферритовой памяти для своего рода защиты: если что не так - программа начинала
выводить из строя машину. А еще такая память не любила - каникул: после
длительного перерыва в работе несколько дней сбоила.
Питание у этой машины включалось ключом (как у автомобиля или мотоцикла)
- круглый такой, который можно было при желании повесить на связку ключей от
квартиры и таскать с собою. Но разумеется так никто не делал - ключ всегда
торчал из передней панели. Вечером выключил машину и ушел домой; утром пришел,
повернул ключ, нажал "продолжить" и программа пошла выполняться с того места,
где вчера остановилась. (По прерыванию от контроля питания все регистры
процессора сохранялись в ОЗУ. А потом по тому-же прерыванию восстанавливались и
программа продолжалась как ни в чем небывало.)
Следующая по мощности и габаритам модель - Электроника-100/25 практически
полный аналог СМ-4, ничем по организации от неё не отличалась - такие-же стойки
со вставленными в них блоками, соединёнными парой кабелей "общей шины". И даже
сигналы в них те же самые, но разъёмы совершенно другие - просто так СМ`овское
оборудование не подключишь. (Разные министерства вишь делали!) На передней
панели процессора - клавишный регистр, организованный примерно так же как и в
Э-100/16.
Самая старшая (и соответственно самая высокопроизводительная) модель всего
семейства - Электроника-79 отличается только процессором (ну очень большой ящик,
а память - еще один - поменьше), а конструктив - такой же в точности. И
соответственно внешние устройства - те же самые. Клавишный регистр на передней
панели процессора тоже продвинутый - не лампочки и клавиши, а семисегментный
индикатор и цифровые кнопочки. Очень маленький и скромный. А основное место на
передней панели процессора занимал отладочный пульт, позволявший в частности
остановить тактовый генератор и прогонять тактовые импульсы даже не поштучно,
а по одному фронту... И к этому - сорок девять альбомов документации на один
только центральный процессор, подробнейшим образом описывающей абсолютно всё -
классная игрушка для начинающего электронщика!
В этом мистическом агрегате, как в квантовой системе, постоянно
присутствовало 0.5 дефекта: т.е. если починить последнюю известную, пусть даже
самую мелкую, неисправность - тут-же ломалось что-то еще. Но если не трогать -
всё исправно работало годами. (Опытным путём пришли к тому что пусть это будет
панелька, в которой воткнуто ПЗУ начального пуска, содержащее тэст процессора и
загрузчики. Оно нужно всего несколько секунд - в начальный момент загрузки
операционной системы. Соответственно, этот процесс выглядел так: набираешь на
клавишном регистре нужный адрес; суёшь левую руку внутрь процессора (для чего
он всегда из стойки несколько выдвинут и лишен боковой крышки) и нажимаешь на
эту микросхему пальцем; другой рукой нажимаешь на кнопку "пуск" - слышишь
характерные звуки, издаваемые например дисководом - всё, можно отпускать.)
А еще нельзя было хвастаться. Идёшь по коридору, а на встречу - зав.кафедрой
профессор Степанов, и спрашивает: как мол там семьдесятдевятка? Если отвечать
уклончиво - то всё в порядке. Но если честно сказать: всё хокей - пашет как
трактор, то... Возвращаешься обратно - висит! Перезагружаешь систему (там у
UNIX`а в суперблоке каждые тридцать секунд фиксируется текущее время) - ну
точно, именно в этот момент и повисла! (Было не менее трёх достоверных
случаев.) Но если соблюдать эти несложные правила, то не машина а прелесть -
вела себя паинькой. Правда было ей скучновато: чем не загрузи, а всё равно
большую часть времени висит на команде wait - это прекрасно видно по
характерному состоянию индикаторов отладочного пульта.
Электроника-60 - это уже не "мини-", а "микро-ЭВМ", предназначенная в
основном для управления каким либо оборудованием. Выглядит как один
небольшой блочок для СМ-овской стойки. По габаритам даже поменьше
системного блока писишки, но скомпонован горизонтально (как и все
промышленные процессоры). Внутри у него справа тяжеленный блок питания, а
слева то, что называется "корзина" - щелевой разъём, на котором и разведена
"общая шина" и в который втыкаются платы процессора, памяти и каких надо
интерфейсов к внешним устройствам из довольно большого набора. А буде нет
ничего подходящего - вот "слепая" плата - паяй что хочешь. На блоке питания
три клавиши: "питание" - включающая машину, "работа" - разрешающая
выполнение программы, и "таймер" - подающий на линию прерывания от таймера
пятидесятигерцовый сигнал от сети переменного тока (а то при желании можно
подключить что-то другое).
Щелевой разъём, кто не знает - под контакты, нарисованные прямо на краю
платы печатным способом (и обычно позолоченные). Используемый в Э-60 разъём
состоит из двух половин по 18 контактов с каждой стороны платы, с фигурным
вырезом между ними - чтобы не вставить её вверх ногами. В корзине по
горизонтали два таких разъёма, а по вертикали - четыре. (Или два раза по
четыре.) Плата с одним разъёмом почему-то считается "половинкой". "Целая"
вставляется сразу в оба. (А у моей любимой Э-79 в процесоре платы были с
такими же в точности разъёмами, но "полуторные", по виду и габаритам более
всего напоминающие снеговую лопату.)
Клавишный регистр эмулируется на системном ("консольном") терминале,
каковой обязательно присутствует по адресу 0177560. Эмулятор клавишного
регистра выдаёт туда символ @ и ждёт, что напишет человек, сидящий за
терминалом. Циферки он рассматривает как ввод числа (восьмеричного), а остальные
символы - как команды. (Или игнорирует.) Основная команда: / (косая черта) -
"открыть" ячейку памяти, т.е. рассмотреть вот только-что введенное число как
адрес, прочесть и выдать на терминал слово, по нему содержащееся. После этого
ожидается либо ВК - закрыть открытую ячейку, либо ПС - открыть следующую. (Ещё
вроде бы использовался символ ^ со значением - открыть предыдущую ячейку.)
Если перед этим ввести число - оно заносится в открытую ячейку как новое
содержимое. Команда @ означает косвенное обращение - открывает ячейку,
используя в качестве адреса содержимое уже открытой.
У машин с аппаратным клавишным регистром, регистры процессора имели адреса
на общей шине, по которым к ним можно было обращаться - как с помощью
клавишного регистра, так и из программы. Да и сам клавишный регистр тоже имел
такой адрес. (Потому так и назван.) А вот при использовании виртуального от
этого отказались: доступ к регистрам общего назначения процессора (РОН) - по
команде R плюс цифра т.е. R0..R7, еще RS - слово состояния процессора (ССП).
(Или про S - уже не помню.) Еще была предусмотрена команда L, эмулирующая
работу начального загрузчика. Только перед ней нужно было указать адрес
устройства, с которого загружать. В более поздних процессорах были введены
еще команды (например в ДВК, на процессоре 1801ВМ1 или 1801ВМ2 добавились
команды T1, T2, Т3 - запуск тестов). Ну и конешно-же команда G ("пуск")
запускающая программу с указанного перед ней адреса, а команда P
("продолжить") - с того, который уже находится в счетчике команд - регистре R7.
Если клавиша "работа" опущена - G только загружает адрес в счетчик команд, а
P приводит к выполнению одной очередной команды, позволяя таким образом
отлаживать программу по шагам. При опускании клавиши "работа", работающая
программа сразу же останавливается с выдачей на терминал адреса остановки и
очередного приглашения @. То же самое происходит при выполнении процессором
команды "стоп", имеющей, кстати, код 0.
Процессоров которыми комплектовалась Э-60 было несколько. Самый первый
М1 - с "базовым" набором команд, таким же как в СМ-3 или Э-100/16. У М2 уже
были "расширенные" команды, в т.ч. с плавающей запятой одинарной точности.
(Аппаратно - тот же самый М1 плюс лишняя ПЗУ микрокоманд.) Оба имели вид
"целой" платы, где кроме собственно процессора (четыре или соответственно
пять крупных БИС 581-й серии соединенных внутренней шиной плюс обвязка на
мелкой логике) дополнительно размещался один банк ОЗУ (8 Кб). Но всё равно
память приходилось подключать отдельно, дополнительно занимая одно или два
посадочных места в корзине. М3 вроде бы был аналогом М1, но "половинка" (на
однокристальном 581ВЕ1) и без ОЗУ. Чем отличался М4 уже не помню... Следующие
процессоры (до М11 включительно) строились на БИС других, более продвинутых
микропроцессорных комплектов (1804, 1811, 1831) и были аналогами старших
моделей - от СМ-4 или Э-100/25 до Э-79. Т.е. кроме диспетчера памяти некоторые
имели отдельный сопроцессор для работы с числами с плавающей запятой двойной
точности, аппаратно выполняющий соответствующие команды, в то время как на
остальных моделях их приходилось эмулировать.
Еще был ряд процессоров (сделанных на однокристальных процессорах серии
1801) использовавшихся в "диалоговых вычислительных комплексах" (ДВК),
задуманных как ПЭВМ. И содержащих на "целой" (т.е. размером с две "половинки")
плате не только процессор и память, но и вообще всё, что ДВКашке нужно для
жизни: и последовательный интерфейс для консольного терминала, и параллельный
- для принтера, и средство связи с ГМД-70-12 - дисковода для восьмидюймовых
гибких дисков, и контроллер для пятидюймовых (собственного контроллера не
имеющих), и даже вроде что-то для грядущего винчестера... Для ДВК-1 этот
процессор засовывался внутрь "фрязинского" дисплея (Электроника-15-ИЕ-00-13),
сделанного на такой же корзине - там как раз было полтора свободных места.
ДВК-2, если ничего не путаю, дополнялась еще половинной платой контроллера
графического дисплея (КГД), подмешивающего к выдаваемому на монитор
видеосигналу, формируемому алфавитно-цифровым дисплеем, еще и бинарную
графическую картинку (1 бит на точку). Доступ со стороны процессора к этой
картинке - элементарно, через два регистра: в первый - номер слова, а через
второй можно это слово прочитать или перезаписать. ДВК-3 уже комплектовался
контроллером цветного графического дисплея (КЦГД), каковой представлял из себя
еще одну такую-же машинку как и основной процессор ДВКашки, связанную с ним
через последовательный интерфейс. Память у этой второй машинки была 18-и
разрядная: каждой точке соответствовало одно слово, по 6 бит на цвет. И слов
таких у неё было то ли 64К, то ли 128К, что давало возможность хранить
картинку с разрешением по-больше, чем у как раз появившихся примерно в то же
самое время писишных контроллеров EGA. Основная проблема (ну или если хотите -
особенность) всего семейства PDP-11 - малый объём адресного пространства -
всего 2^16=64Kб. В это адресное пространство 128К видеоОЗУ поместиться
конешно-же не могло, и включалось только краешком (вроде как только первые 32Кб,
и разумеется только 16 бит из 18). А всё целиком - через два регистра, так же
как и у КГД. (Некоторые КГДшные графические игрушки, малость переделанные под
КЦГД, загонялись прямо в контроллер и позволяли гонять чертей по экрану без
участия центрального процессора. А центральный процессор ДВК-3, если ничего не
путаю, вполне мог быть уже М4, т.е. с полноценным диспетчером памяти. То есть на
этом процессоре уже можно было запускать например ОС UNIX (который, как
известно, без аппаратной поддержки механизма виртуальной памяти работать не
может). Тем паче некоторые экземпляры стали уже оснащаться как раз появившимися
тогда у нас винчестерами...
К сожалению эта машина элементарно опоздала. Пойди она в серию буквально на
пару лет раньше (и/или будь на то государственная воля) - удалось бы избежать
наплыва писишек: в качестве ПЭВМ её хватило бы еще надолго. А дальше (в плане
решения проблем с маленьким адресным пространством) возможны варианты от
микро-VAX`а - следующей уже 32-х разрядной разработки той же фирмы, до
собственной оригинальной многопроцессорной системы МАРС (ориентированной на ЯВУ,
в частности Модула-2), которая к тому моменту уже выпускалась мелкой серией и
выполнена была как раз в том же самом конструктиве...
В семействе аналогов PDP-11 имелся так-же совсем маленький и дешевый
"бытовой" компьютер БК-00-10 (и его развитие БК-00-11) - ценой примерно как у
телевизора - коробка с плёночной клавиатурой прямо на верхней крышке
(по-мне так жутко неудобной). В качестве дисплея к нему подключался
обыкновенный телевизор, а в качестве средства хранения данных - бытовой
магнитофон.
Имелось для учебных целей нечто подобное (названия не припомню), но
двухпроцессорное. Второй процессор - для графики и интерфейсов, в т.ч.
локальной сети на предмет подключить целый класс таких машинок к более мощной
(и главное - снабженной дисководами) учительской машине (типа той-же ДВК) на
предмет загрузки программного обеспечения, ну и сохранения (а так же контроля)
чего там ученики понапишут.
Имела место быть так же и цельнотянутая с Professional-350 полномасштабная
ПЭВМ Электроника-85. Ни с чем кроме самоё себя аппаратно не совместимая,
закрытая конструкция по габаритам не отличающаяся от писишки и в точности так-же
снабженная цветным графическим дисплеем и винчестером. Правда не очень
большим - всего на 5 Мб (какие тогда были). Однако чтобы развести там UNIX
(точнее ОС Демос-1) этого вполне хватало, но юзерам приходилось ходить со
своими дискетками. (Нам, избалованным терабайтами, сейчас уже сложно оценить
много это 5 мегабайт или мало? Для мелкой одно- (точнее полутора-задачной)
операционной системы реального времени типа RT-11 (не просто аналог, а еще и
"бабушка" МС-ДОС`а) - очень много. Ощущение подобное описанному в своё время
Николаем Василичем (правда по другому поводу): "...а печатной бумаги развелось
столько, что и не придумаешь скоро, что бы такое еще в неё завернуть". Но UNIX -
это ведь БОЛЬШАЯ операционная система (говорили: "самая маленькая из больших") и
на пяти мегабайтах ей тесновато. Однако дистрибутив того же самого первого
Демоса вместе со всеми описаниями и всеми исходными текстами абсолютно всего что
в нём было - занимал всего одну десяти-мегабайтную ленту. (Правда для второго -
три ленты, хоть и неполные.) А СМ-1420 с приводом DM на 14 Мбайт (и парой DK на
два с половиной) под управлением второго Демоса уверенно обслуживала
терминал-класс на шестнадцать посадочных мест. (Именно столько в одном
мультиплексоре каналов.) И там на нехватку дискового пространства уже никто не
жаловался...
Еще в оном семействе были какие-то ни разу не попадавшиеся мне на глаза
специализированные ЭВМ, в т.ч. для управления станками с ЧПУ.
Но всё это, так сказать, вид снаружи.
Рассмотрим (наконец!) что же такое из себя PDP-11 представляла изнутри.
Итак: PDP-11 это ЭВМ изначально предназначенная для решения задач
управления чем ни будь железным в реальном масштабе времени. (Всё ихнее
"третье поколение" - т.н. "мини-ЭВМ" именно под это и заточено.)
Основное требование - быстрая реакция на внешние события. Следовательно -
развитый механизм прерываний. Так-же необходимость подключения самого
различного оборудования, в т.ч. и существенно нестандартного. Отсюда -
"открытая" архитектура в виде интерфейса "общая шина", которая и соединяет
собою всё что в эту машину входит - и процессор и память и все контроллеры
внешних устройств. (Каждое устройство выглядит со стороны этого интерфейса в
точности так же как и память - в виде одной или нескольких адресуемых ячеек.)
Решение задач вычислительного характера - по остаточному принципу. Поэтому
разрядность машинного слова по сравнению с "бухгалтерскими" машинами второго
поколения значительно снижена, что позволило сократить объём, стоимость и
повысить надежность аппаратуры. Система команд тоже ужата до необходимого
минимума и сделана по-возможности "ортогональной" - т.е. такой что
неукоснительно соблюдаются несколько принципов как-бы "перпендикулярных" друг к
другу. (Например чтобы любая команда была применима к любому имеющемуся в
процессоре регистру с использованием любого "метода адресации".)
Но так как к задачам "реального времени" относится и реакция на
кнопконажимание пользователей, рассевшихся за терминалами, т.е. "диалоговые"
задачи, то данный класс машин стал постепенно захватывать и эту область
(изначально бывшую вотчиной "больших" ЭВМ), для чего стали появляться
"старшие" модели с расширенным набором команд, средствами ускорения вычислений
и защиты памяти. И вообще с повышенным количеством ресурсов всех видов.
Центральной частью вычислительной системы, связывающей её в единое целое,
является магистральный интерфейс, известный как "общая шина". Она включает шину
данных Д (16 разрядов), шину адреса А (16, 18 или 22 разряда - у старших
моделей) и различные управляющие сигналы. (Всего что-то типа 56 линий.) В том
числе два особо важных: СИА - "синхронизация активного устройства" и СИП -
"синхронизация пассивного". Т.е. одно из устройств является "активным" -
инициатором обмена данными. Его еще иногда называют "задатчиком". (Чаще всего
это центральный процессор, но не обязательно.) Оно выставляет адрес. А то
устройство, чей это адрес, выступает в роли "пассивного", его еще иногда
называют "исполнителем".
Обмен информацией по магистрали происходит асинхронно - по принципу
"рукопожатие". Начинается он с того, что задатчик выставив на шину А адрес,
извещает всех об этом передним фронтом сигнала СИА. (Говорит: эй ты, такой-то
такой-то!) Обнаружив это, каждое пассивное устройство должно проверить - не к
нему ли задатчик обращается? Если да, то ответить передним фронтом сигнала СИП.
(Типа: я вас слушаю, сэ-э-эр!) Активное устройство ждёт появления СИП в течении
некоторого "времени таймаута" (довольно большого: шина может быть длиной до десяти
метров). Если не дождалось - считает что устройства с таким адресом нет, и
предпринимает меры. (Это вам не писишка с синхронной шиной: в первом тактовом
интервале задатчик выставил адрес, а во втором тупо считал что-то с шины данных.
А уж выставил туда кто-то что либо осмысленное или нет - его не касается.)
Если устройство откликнулось - дальше всё зависит от намерений задатчика.
Каковых может быть три, известных как циклы шины: "чтение", "запись" и
"чтение-пауза-запись". Задатчик извещает всех об этом заранее, выставляя
соответствующий 2-х битный код одновременно с адресом.
Если предполагается запись, то задатчик выставляет данные на шину Д
одновременно с адресом, а исполнитель передним фронтом СИП извещает его, что
данные считаны и их можно убирать. Услышав это, задатчик убирает сигнал СИА
и теперь ему остаётся дождаться заднего фронта СИП - и можно переходить к
следующему обращению. (Хотя здесь я не очень уверен: вполне возможно, что для
унификации с циклом "чтение-пауза-запись" брать информацию с шины Д исполнителю
надлежит не по переднему, а по заднему фронту СИА. Для магистрали с раздельными
шинами адреса и данных это без разницы, но есть вариант магистрали с
совмещенными шинами (вот как раз у Э-60) - там выставить одновременно и адрес и
данные никак не получится - шина-то одна. Поэтому там всё сложнее.)
Если предполагается чтение, то исполнитель должен выставить слово данных
вместе с передним фронтом СИП, а задний фронт СИА указывает ему что их можно
уже убирать.
Третий тип цикла шины заточен под такие машинные команды, в которых
результат помещается по адресу операнда. И к тому-же использует особенности
энергонезависимых запоминающих устройств на ферритовых колечках: у них чтение -
разрушающее. То есть информация там хранится в виде направления намагниченности.
И чтобы считать информацию - надо перемагнитить все колечки в одну сторону.
Тогда с тех, что были намагничены по-другому будет импульс, а с тех чьё
состояние не изменилось - нет. И вот после этого надо записать только-что
считанную информацию обратно. Но ведь записать можно уже новое значение, только
чуть-чуть подождав до окончания вычислений. (Тем паче что память всегда
работает медленнее чем процессор.) Здесь, как и при чтении, исполнитель
выставляет старое значение вместе с передним фронтом СИП, а новое должен
прочитать по заднему фронту СИА. Но ведь старое значение он с шины Д еще не
убрал! Убирает, ждёт некоторое время чтобы закончились переходные процессы,
считывает и использует по назначению новое значение с шины Д и только после
этого убирает сигнал СИП - сигнал для активного устройства, что значение с
шины Д можно наконец убирать.
Таким образом в течении некоторого времени на шину Д выставляют информацию
одновременно два устройства. Это вполне допустимо, т.к. шина организована по
принципу "монтажное ИЛИ" (на элементах "с открытым коллектором"). Т.е. её линии
подтянуты к питанию специальными резисторами, находящимися на их концах. (И
служащими так-же в качестве "терминаторов", предотвращающих отражение
электромагнитной волны от конца линии.) В результате логический ноль
обозначается высоким уровнем сигнала, а логическая единица образуется если
"посадить" линию на землю. Что без всякого ущерба друг для дружки могут
одновременно сделать несколько устройств.
Это-же активно используется, например, при требовании прерывания:
устройство, желающее чтобы его обслужили, просто сажает общую для всех линию
требования прерывания на землю. И отпускает только когда его обслужат. Но
процессору надо узнать, кого обслуживать - для этого и служат "вектора
прерывания". Номер своего вектора устройство сообщает получив сигнал
"подтверждение прерывания" - просто выставляет соответствующее число на
шину данных (после чего снимает требование). Но сигнал подтверждения прерывания
разрывный: если устройству ничего от процессора не надо, оно просто
воспроизводит на выход (следующему устройству в цепочке) то что пришло ему по
этой линии. А если оно желает прерывания - то не воспроизводит. Свой вектор
выставит то устройство (из желающих оного), до которого это "подтверждение"
дошло. Т.е. приоритет убывает по мере удалённости от процессора. Как
регламентируются моменты выставления и убирания номера вектора и какие сигналы
в этом участвуют, вспоминать не буду - догадайтесь сами.
У младших моделей - одна, общая для всех линия запроса прерывания; у старших
моделей несколько - с разным уровнем приоритета.
Активным устройством может быть не только процессор, но и кто угодно - для
этого на шине имеется специальное устройство - "арбитр", заведующий
предоставлением доступа к ней. Что даёт возможность как "прямого доступа к
памяти" для особо продвинутых устройств, так и возможность для совместной
работы нескольких процессоров. Как конкретно производится доступ желающих к
управлению магистралью вспоминать не будем - для дальнейшего изложения это не
принципиально.
Внимание: я рассказал только общий принцип передачи данных по общей шине как
сам его помню. В реальности её протокол несколько сложнее. Особенно для варианта
с совмещенной шиной адреса и данных. В частности, там есть отдельный сигнал,
указывающий на передачу одного байта (а не целого слова), и отдельный сигнал,
указывающий что обращение идёт к внешнему устройству (для упрощения дешифратора
адреса). И главное - данные там выставляются и убираются не по фронтам сигналов
СИП и СИА, а по фронтам сигналов ВВОД и ВЫВОД - в которые превратились два бита,
указывавшие тип шинного цикла. В результате на совмещенной шине данных-адреса
нету временных интервалов, когда информацию на неё выставляют одновременно два
устройства. Это позволяет при необходимости сделать её на элементах "с тремя
состояниями" а не "с открытым коллектором", что для одноплатных систем, где
длина проводников - сантиметры, значительно удобнее.
А в цикле чтение-пауза-запись сигнал СИП там выдаётся два раза - при выдаче
слова данных и при получении. Вот и возникли у меня смутные сомнения:
действительно ли я пересказал существующий протокол а не изобрёл по ходу
пересказа свой собственный?
В качестве примера типичного внешнего устройства рассмотрим последовательный
интерфейс, к коему подключался уже упоминавшийся консольный терминал. Прочие
подобные устройства (все они по классификации UNIX`а - "байтовые", т.е типа Ц)
такие-же в точности.
На самом деле это два устройства: устройство ввода, к которому подключена
клавиатура, и устройство вывода - к нему подключен буквопечатающий механизм.
Как именно подключен - не важно. Ну скажу, что две последовательные линии связи
типа "токовая петля" - в ту и в другую сторону со старт-стопным протоколом
(коий я уже описывал в главе 15). То есть аналог писишного ком-порта, но с
гальванической развязкой и дальностью связи не пара метров а раз в сто больше.
Подключение может быть и параллельное (как для принтера) - со стороны машины
всё это выглядит совершенно одинаково.
Каждое из устройств - два регистра: регистр состояния и регистр данных.
В регистре состояния задействованы всего два бита: старший ("знаковый") бит
байта по адресу 177560 - признак готовности, указывающий, что в регистре данных
- байте по адресу 177562 - код очередной нажатой клавиши. При его считывании
признак готовности автоматически сбрасывается. Следующий после него бит -
разрешение прерывания. Если он установлен, то при установке признака готовности
устройство начинает требовать прерывание по вектору 60. В регистре состояния
могут быть задействованы и другие биты. Чаще всего присутствует признак
переполнения - старший (опять же знаковый) бит всего слова по адресу 177560. Он
устанавливается когда пришел следующий байт, а предыдущий еще не считан.
Устройство вывода устроено в точности так же, но работает в обратную
сторону: там признак готовности (в младшем байте по адресу 177564)
устанавливается когда оно готово принять очередной байт, и автоматически
сбрасывается при записи чего либо в регистр данных по адресу 177566. Вектор
прерывания у него 64.
Разновсякие параметры последовательного интерфейса (скорость, количество
стоповых бит, наличие контроля четности), а так же адрес и вектор -
устанавливаются перемычками на плате устройства.
Интерфейс принтера или чего ни будь типа перфоратора не отличаются
решительно ничем (кроме адреса и номера вектора). А интерфейс перфосчитывателя
имеет еще один бит в регистре состояния (самый младший) - при записи в него
единички побуждающий перфосчитыватель считать очередной байт. Т.к. в отличии от
сидящего за клавиатурой человека, сам он инициативу проявить не может.
Признак готовности (да и ошибки) не просто так размещен в старшем "знаковом"
разряде - так его гораздо проще проверять ("на минус"). А бит "активации" -
тоже не зря в самом младшем: записать в этот разряд единичку не изменяя всех
остальных - элементарно: с помощью команды inc, читающей содержимое ячейки,
прибавляющей к нему единичку и тут-же записывающей обратно. (Читается из этого
младшего бита, разумеется, всегда ноль.)
Вот мы наконец и добрались до центрального процессора (ЦП). "Центральный" он
потому, что возможны еще и "периферийные" процессоры. Например процессор (он же
"канал") ввода/вывода. Но разумеется не в этой архитектуре.
ЦП архитектуры PDP-11 - типичная "регистровая" машина: имеет восемь
совершенно одинаковых регистров общего назначения (РОН), обозначаемых обычно как
R0..R7. И еще так называемое "слово состояния программы" (ну или процессора -
ССП), содержащее признаки. Этот набор признаков плюс содержимое РОН - полностью
определяет состояние выполняющейся в данный момент программы. Если их
аккуратненько где ни будь сохранить, а потом через некоторое время восстановить
на место - программа "ничего не заметит" и будет спокойно выполняться дальше.
(Если конешно не попортить используемые ею области памяти.)
ССП в минимальном варианте содержит четыре признака результата предыдущей
операции (NZVC - вот именно в таком порядке), один бит разрешения прерывания, и
еще один признак Т, используемый при отладке. А в максимальном (для старших
моделей) варианте бит разрешения прерывания превращается в три - уровень
"приоритета" выполняемой в данный момент программы, и добавляются два комплекта
по два бита - коды привилегированности текущего и предыдущего режимов работы.
Приоритет - это номер линии запроса прерывания, запросы с которой уже не
рассматриваются. (Правда линий по-максимуму четыре, а приоритетов - восемь.)
А режим работы это фактически номер задействованного в данный момент диспетчера
памяти, каковых у самой старшей модели за каким-то контрабасом три штуки. (А
еще у неё раздельные адресные пространства для программы и данных. Правда на
слове состояния процессора это никак не отображается.) Но впрочем никаких
(столь характерных, например для писишки) "исторических наслоений" (ни в ССП,
ни где либо еще) - следов того, как разработчики тужатся втиснуть в конструкцию
нечто, заранее не предусмотренное. Что в свою очередь - результат ранее
принятых сиюминутных решений; нежелания (а то и неспособности) думать на
перспективу. Впрочем и никакой дурной избыточности - результата борьбы
бюрократическими средствами с предыдущим явлением (по принципу: "вали кулём -
потом разберём!"), расплачиваться за которую приходится, разумеется, потребителю.
Все битики красиво и четко распределены заранее - еще в момент закладки всего
модельного ряда.
И регистры и ССП и слова, которыми процессор обменивается по шине - все
шестнадцатиразрядные. Адресуется каждый байт, но за раз передаётся всё равно
целое слово. По-этому для данной архитектуры важно чтобы информация была
выровнена по словам. Байт-то читается и пишется по любому адресу, а при
обращении к целому слову младший бит адреса просто игнорируется.
Все регистры - совершенно равноправные, но два из них дополнительно
используются для специальных целей: R7 - как счетчик команд, а R6 - как
указатель аппаратного стэка. Тоесть при выборке из памяти очередной команды,
адрес для этого берется из R7, после чего его содержимое увеличивается на два.
Адрес этот обязательно должен быть четный (иначе ошибка), потому как команда
тоже целиком занимает двухбайтовое слово. Адрес в регистре R6 используется
аппаратурой в двух ситуациях: там (на аппаратном стэке) при прерывании
сохраняется самая необходимая информация о состоянии процессора - два слова:
содержимое регистров R7 и ССП; и одно слово - при переходе к подпрограмме. Адрес
в регистре R6 при этом автоматически уменьшается (на 4 и на 2 соответственно) -
в этой архитектуре стэк растёт в сторону младших адресов. Запись на стэк и
чтение с него тоже производится исключительно целыми словами. (Младший бит R6,
если он вдруг единичка, просто игнорируется.) Но в остальном эти два регистра
решительно ничем не отличаются от всех остальных.
PDP-11 машина двухадресная в том смысле, что результат выполнения команды
помещается на место второго операнда. Или единственного - одноадресные команды
там разумеется тоже есть. (В расширенном наборе есть так-же полутора-адресные
команды: кодов на всё что надо не хватило и один операнд получился
"сокращенный" - только номер регистра.)
В отличии от машин первого и второго поколения с их машинными словами
немеряной длины и при том довольно коротким адресом (с оперативной памятью в
те времена были большие проблемы), позволяющим запихнуть их в машинное слово
несколько штук - здесь такой фокус при всём желании не получится: размер адреса
равен размеру команды. Поэтому вся адресация производится через регистры. Каждый
операнд занимает шесть бит: три бита - номер регистра, и еще три - "метод
адресации" указывающий что с этим регистром делать. Этих самых методов адресации
- восемь: четыре "основных" и четыре "более косвенных": где операнд предыдущего
используется как адрес.
0 (000) - регистровый - обозначается Rx, где x - номер регистра
1 (001) - косвенно регистровый - обозначается @Rx или (Rx) - можно так и так
2 (010) - инкрементный - обозначается (Rx)+, в регистре адрес, который ПОСЛЕ
его употребления увеличивается: на единицу - если обращение к байту или на два
если обращение к слову. Но R6 и R7 - всегда на два.
3 (011) - косвенно-инкрементный - обозначается @(Rx)+
4 (100) - декрементный - обозначается -(Rx), уменьшение адреса в регистре
производится ПЕРЕД его использованием для получения операнда
5 (101) - косвенно-декрементный - обозначается @-(Rx)
6 (110) - индексный - обозначается Е(Rx), где Е - вот этот самый индекс,
занимающий следующее слово после команды. Индекс складывается с содержимым
регистра (который при этом никак не изменяется) и обращение происходит по
полученному таким методом адресу. Типа: индекс - адрес начала массива, а в
регистре - смещение его элемента, к которому надо произвести обращение.
7 (111) - косвенно-индексный - обозначается @Е(Rx)
Таким образом двухадресная команда на самом деле может занимать до трёх слов.
Так как в этой машине всё по восемь - естественной для неё является
восьмеричная система счисления. Шестнадцатиразрядное слово делится по три бита,
каждые из которых превращаются в одну восьмеричную цифру. И вся система команд
организована так, чтобы поля командного слова приходились по границам
восьмеричных цифр - ну на сколько это вообще возможно. В частности самый старший
"знаковый" бит, оставшийся в гордом одиночестве, по-возможности используется как
однобитное поле, например указывающее размер операнда.
Итак, слово - 16 бит; команда занимает его целиком. Каждый "полноформатный"
операнд это номер регистра (одна восьмеричная цифра, т.е. три бита) и метод
адресации, указывающий что с этим регистром делать. Итого 6 бит. Для двух
операндов надо 12. Еще один бит (самый старший, как было сказано выше) указывает
размер операнда. В результате на код операции остаётся одна восьмеричная цифра
- 3 бита. Тоесть двухадресных полноформатных команд может быть только восемь.
А если учесть что нужны так же и другие команды (в частности для управления
порядком выполнения действий) и под них надо бы тоже зарезервировать парочку
кодов (0 и 7) - маловато получается. Но ничего не поделаешь - машинное слов не
резиновое. Выглядит это так (одна буква - восьмеричная циферка):
rКaabb где К - код операции, r - размер операнда: aa и bb - операнды A и B.
1 (001) - MOV A,B - пересылка A -> B или B=A
2 (010) - CMP A,B - сравнение (A-B)?
3 (011) - BIT A,B - сравнение побитовое (A&B)?
4 (100) - BIS A,B - установка битов (операция ИЛИ) A|B -> B или B|=A
5 (101) - BIC A,B - сброс битов (операция И-НЕ) ~A&B -> B или B&=~A
а вот здесь кодов не хватило - пришлось занять под код операции бит r
06 (0110) - ADD A,B - сложение (только для слов) A+B -> B или B+=A
16 (1110) - SUB A,B - вычитание ( --//-- ) B-A -> B или B-=A
Здесь A и B - операнды такого вида, как описано выше. То есть в кодах каждый
из них - две восьмеричные цифры, код операции - еще одна, и последняя самая
старшая, на которую пришелся всего один бит - размер операнда (0 - слово, 1 -
байт). Для команд сложения и вычитания этот бит - часть кода операции (увы),
а сами эти команды - только с целыми словами. Впринципе (с учетом одноадресных
команд) этот набор функционально-полный. Но без команды исключающее-ИЛИ всё-же
как-то кисло...
Примеры:
MOV R2,R5; код 010205 - пересылка содержимого регистра R2 в регистр R5
SUB R2,R5; 160205 - вычитание этих-же регистров, т.е. R5 -= R2;
BIC R2,R5; 050205 - сброс битов по маске - R5 &= ~R2;
BIS @R2,R5; 041205 - побитовое ИЛИ - R5 |= *R2;
BIS R2,(R5); 040215 - *R5 |= R2;
BIS (R2)+,R5; 042205 - R5 |= *R2++;
BIS @-(R2),36(R5); 045265 000036 - (два слова) - *(R5+036) |= **--R2;
BIS @4(R2),36(R5); 047265 000004 000036 - (три) - *(R5+036) |= **(R2+4);
В ассемблерных мнемониках однобайтовый размер операнда указывается
добавлением после названия команды буковки Б-латинская. Например:
BIS (R2)+,R5; код 042205 - операция с целыми словами
BISB (R2)+,R5; 142205 - операция с байтами; содержимое R2 здесь
увеличивается не на 2 как в первом случае, а только на 1.
BISB @(R2)+,R5; 143205 - здесь опять на два, т.к. R2 указывает на адрес
BISB (R6)+,R5; 142605 - и здесь тоже на два: т.к. R6 - указатель стэка,
а в стэке даже один байт должен занимать целое слово
Операнды обозначаются не только как описано чуть выше, но и с
использованием вводимых в ассемблере имён. Каковые имена в основном заменяют в
нём адреса ячеек памяти (на момент написания программы еще неизвестные) и
играют ту же самую роль, что и метки в языке Си. По крайней мере вводятся в
точности так же - имя и после него двоеточие. Но в Си роль метки сильно
ограничена - единственное что с нею можно сделать - использовать в операторе
goto. Имена переменных тоже "помечают" собою отведенные под них ячейки памяти
(хотя и вводятся совершенно по-другому), ну так в ассемблере между ними и
метками разницы нет - машина же не различает что в ячейке - подлежащая обработке
информация или предназначенная для этого команда. За сим будущую переменную
точно так же помечаем меткой как и команду, коей собирается передавать
управление, а чтобы застолбить под неё место - пишут там начальное значение
(с помощью специально для этого предназначенной ассемблерной директивы). Но
можно и напрямую сопоставить (присвоить) число имени, например адреса регистров
консольного терминала. Хотя по-мне - указать его в явном виде - понятнее.
(Двойная работа: вместо одного числа (одного и того-же на все времена) нужно
еще запоминать и обозначающее его имя, у каждого автора своё собственное!)
Однако, рассматривая вышеперечисленные методы адресации, остаётся непонятным
как работать с константами и с прямыми адресами. То есть чтобы обратиться
например к тому-же консольному терминалу, его адрес (177560) уже должен быть в
одном из регистров процессора. Но как же он туда попадёт? А вот с помощью
такого любопытственного фокуса-покуса:
Вспомним, что регистр с самым большим номером R7 это счетчик команд. Перед
началом выполнения очередной команды он содержит её адрес. Далее команда
считывается, адрес в R7 увеличивается на два и указывает на следующее слово
после команды. И вот теперь эта команда начинает выполняться... Ну так в
процессе её выполнения можно сделать с любым регистром (в том числе и с R7) всё
что нам вздумается! Например MOV R7,R3; (код 010703) копирует в R3 адрес
следующей команды, а MOV (R7),R3; (код 011703) - её саму (уж не знаю зачем).
Соответственно MOV R3,(R7); (код 010317) - наоборот вписывает на место следующей
команды содержимое регистра R3. (Если конешно эта программа не прошита в ПЗУ -
тогда фокус не получится.) Команда MOV R3,-(R7); (код 010347) подменит саму
себя, после чего будет выполнена по-новой, т.к. в R7 после её выполнения
останется её-же адрес...
Но самый полезный фокус заключается в следующем: команда читает следующее
после себя слово и сразу же его пропускает чтобы не выполнять как команду.
Методы адресации 27 и 37, в смысле (R7)+ и @(R7)+. Первый позволяет
использовать это слово как константу, второй - как АБСОЛЮТНЫЙ адрес. (Если
вспомнить еще про "индексные" методы адресации 67 и 77 тоже использующие
дополнительное слово, то из них получается обращение по ОТНОСИТЕЛЬНОМУ адресу,
позволяющему писать "перемещаемые" программы, которым безразлично в какое место
ОЗУ их загрузили.) В ассемблере эти фокусы узаконены в том смысле, что для них
введено удобное отдельное обозначение: непосредственный операнд обозначают
символом # а абсолютный адрес - символом @. Например:
MOV #123456,R3; код 012703 123456 - загрузить в R3 само число 0123456
MOV @123456,R3; 013703 123456 - загрузить туда нечто по этому адресу
MOV 123456,R3; 016703 ?????? - впринципе то же самое, но каким получится
содержимое второго слова команды сильно зависит от того, по какому адресу
окажется сама эта команда. Поэтому так обычно не делают - пишут что-то типа:
MOV AAABBB,R3; где AAABBB - имя переменной - в смысле метка ячейки памяти,
которая если что - будет переезжать по памяти вместе кодом программы.
Еще с невесть каких времён известно, что "в любой программе найдётся хотя бы
одна ошибка". Вот народ (мои шефы, а сам я тогда еще был студентом) и говорит:
как же так, неужели действительно в абсолютно любой? (Ну, разумеется только
пока еще эта программа не отлажена.) Ну а если программа эта -
маленькая-маленькая? Самая маленькая, какая вообще может быть - вообще из одной
команды! Проверим? И проверили (а я при том присутствовал).
Надо сказать, что придумать нетривиальную программу, которая бы состояла
всего из одной команды - задача сама по себе весьма нетривиальная. Но на наше
счастье в (переводном) журнале "в мире науки" попалась любопытственная статья,
содержащая описание компьютерной игры с условным названием "борьба за память".
Смысл этой игры вот в чем: имеется предельно простая абстрактная вычислительная
машинка; два программиста пишут две боевые программы; они загружаются в её
память и выполняются параллельно - выигрывает та программа, которая сможет
испортить другую. Вернее проигрывает та, которая первой выполнит команду СТОП.
В статье приводилось описание этой самой вычислительной машины и примеры
нескольких боевых программ для неё. Архитектура машинки предельно проста:
- никаких внешних устройств и средств ввода/вывода - машинка только для игры
- система счисления - десятичная
- система команд - трёхадресная: слово как раз такой длины, что в нём
помещается код операции (одна десятичная цифра) и три адреса
- размер адреса вроде-бы четыре цифры, т.е объём памяти - 10 килослов
- адресация - исключительно "относительная", т.е. любой адрес указывает
расстояние между адресуемой ячейкой памяти и командой
- команд всего десять штук; конкретные коды команд не помню (при
необходимости можно придумать заново); код команды СТОП, разумеется, ноль.
Трёхадресная система команд не предполагает никаких регистров: первый адрес
указывает где взять первый операнд, второй - где второй; третий - куда положить
результат. Абсолютных адресов нет - программа совершенно одинаково работает в
любом месте оперативной памяти и не имеет никаких средств узнать, где именно она
находится. Память, соответственно "циклическая", т.е. после ячейки с самым
большим номером следует ячейка с нулевым.
В статье было описано несколько боевых программ. Например "карлик" - всего
четыре команды: разбрасывает по памяти нулевые "бомбы", но с таким шагом чтобы
не попасть в самого себя. Программа "блоха", скачущая по памяти, копируя свой
код в другое место. Программа "телескоп" - не агрессивная, способная только
защищаться: она организует справа и слева от себя два "форпоста" и постоянно их
проверяет. Если один из них окажется испорчен, например в результате того, что в
него попала одна из нулевых бомб, разбрасываемых "карликом" - копирует себя за
зону возможного обстрела, а форпосты - восстанавливает. Ну и наконец - программа
"чертёнок", состоящая ровно из одной команды. Эта команда просто копирует самоё
себя в следующую ячейку - ту самую в которой следующая команда, и таким образом
заполняет собою всё память.
Вот Николай Иванович Муравьёв и говорит: а зачем нам абстрактная
вычислительная машина - у нас же конкретная есть (и кивает на Э-100/16) -
давайте напишем "чертёнка" для неё. И написал. Ну так в процессе написания этой
программы действительно была допущена ровно одна ошибка. А в уже отлаженном
виде вся эта программа выглядит так: 014747.
Вышеизложенной информации вполне достаточно чтобы понять как она работает.
Ну-с, думаю с методами адресации всё понятно?
Тогда рассмотрим остальные команды. Как уже было сказано, под них
отведены коды 0 и 7, т.е.:
Х0ХХХХ или если восьмеричные цифры расписать по битам: Х 000 ХХХ ХХХ ХХХ ХХХ
Х7ХХХХ Х 111 ХХХ ХХХ ХХХ ХХХ
Следует сразу сказать, что комбинация 17ХХYY зарезервирована для команд
процессора с плавающей запятой (ППЗ). Все они максимум полутора-адресные, т.е.
полноценный операнд (использующий регистры основного процессора) только один,
помещается в разрядах, обозначенных YY. А расположенный перед ним номер
регистра ППЗ (если есть) занимает всего два бита.
Соответственно под все остальные команды процессора остались комбинации типа:
07ХХХХ и Z0XXYY где один "знаковый" бит, приходящийся на последнюю цифру и
обозначенный здесь Z может, как и в двухадресных командах, использоваться как
признак размера операнда; YY указывать сам операнд, а биты XX использоваться под
код операции. Именно так одноадресные команды и сконструированы. Под них
заняты коды операций 5Х и 6Х:
r0ККaa здесь r - размер операнда, aa - сам операнд A, а КК - код команды:
50 CLR A "очистка" - просто записывает в A число 0: 0 -> A
51 COM A "инверсия" - инвертирует все биты своего операнда: ~A -> A
52 INC A "инкремент" - прибавляет к нему единичку: A+1 -> A
53 DEC A "декремент" - вычитает: A-1 -> A
54 NEG A "негатив" - смена знака на противоположный ~A+1 -> A
55 ADC A прибавляет к операнду признак C (или не прибавляет, если он ноль)
56 SBC A то же самое только вычитает: if(C)A++; и соответственно if(C)A--;
57 TST A берёт операнд и ничего с ним не делает - только устанавливает
признаки результата - биты NZVC в слове состояния процессора
60 ROR A "циклический" сдвиг вправо (в сторону младших разрядов)
61 ROL A --//-- влево (в сторону старших)
62 ASR A "арифметический" сдвиг вправо - эквивалентен делению на два
63 ASL A --//-- влево - --//-- умножению --//--
Вот собственно почти все "содержательные" команды базового набора. (Ну
разве что еще SWAB - обмен байтов в слове с кодом 0003aa, и SXT - расширение
знака с кодом 0067аа.)
Примеры:
CLR R3; код 005003 - просто очищает регистр R3
COMB (R3)+; 105123 - инвертирует байт по адресу в R3 (а R3 = R3+1)
INCB (R7)+; 105227 - наращивает однобайтовый счетчик, находящийся прямо
сразу после команды (но R7 всё равно увеличивается не на 1 а на 2!)
ROR R3; код 006003 - сдвигает все биты регистра R3 на одну позицию в
сторону младших разрядов; в самый старший бит задвигается содержимое
признака C (того, который "перенос"), а самый младший, выдвинутый за пределы
разрядной сетки, помещается в него-же. И следующей такой-же командой может быть
например задвинут в старший разряд следующего слова...
ROL R3; код 006103 - всё то же самое, но в другую сторону
ASR R3; 006203 - здесь сдвигается вправо только 15 бит: старший
"знаковый" разряд остаётся где был и даже копируется в следующий после него.
ASL R3; код 006303 - здесь в признак C переезжает предпоследний разряд, а
"знаковый" тоже остаётся где был; в младший разряд задвигается ноль.
То, что старший разряд слова (да и байта) - "знаковый" упоминалось
неоднократно, но видимо следует пояснить, что числа со знаком в данной
вычислительной машине (да и в подавляющем большинстве других) представляются в
т.н. "дополнительном" коде.
Тут вот какое дело: сами по себе битики, составляющие машинное слово, ничего
не значат. (Спасибо еще что упорядочены.) Смысл им придаёт тот, кто использует.
Но если в троичной системе счисления, где каждый разряд машинного слова "трит"
принимает не два а три значения (такая машина была в мире только одна: Сетунь и
её развитие Сетунь-70 им тов. Бруснецова) и там числа автоматически получаются
со знаком (ибо эти три значения наиболее естественно интерпретировать как +1, 0
и -1, а числа с плавающей запятой там - вообще отдельная песня), то для двоичной
системы счисления подобный фокус проделать невозможно. И чтобы пользоваться не
натуральными (т.е. только положительными, правда включая ноль), а и целыми
числами (то есть со знаком) приходится предпринимать искусственные меры. Разные.
Самая простая и естественная - выделить под знак числа один из разрядов
машинного слова - самый младший, или самый старший или вообще находящийся за
пределами разрядной сетки - не важно. Важно как такое "целое" (т.е. знаковое)
число будет сочетаться с "натуральным" (т.е. беззнаковым) и как мы с ними со
всеми намерены производить операции. Хотя бы самую простую - сложение.
Если просто выделить один разряд - как правило самый старший - чтобы не
мешался под ногами и чтобы положительное знаковое число совпадало с беззнаковым,
то во-первых у нас будет два нуля - положительный и отрицательный, а во-вторых
для сложения понадобятся два устройства - сумматор и вычитатель (потому как
сложение чисел с разными знаками это вычитание). Но так иногда делают. Это
кстати называется "прямой код". Однако есть интересный фокус-покус, позволяющий
обойтись одним устройством и для сложения и для вычитания - его-то все и
используют: хранят отрицательное число в виде т.н. "дополнительного кода".
Предположим, что у нас уже есть какое-то положительное число и мы хотим
сменить ему знак на противоположный. Предположим так-же что это число имеет
бесконечное число разрядов. Вот только само число конечное, и все разряды с
номерами больше некоторого N равны нулю. А разряд N, соответственно единичка.
(Хотя, если число - ноль, то ни одной единички в нём нет.) Ну так вот,
преобразуем его в дополнительный код следующим образом: все биты инвертируем и к
тому что получилось прибавляем единицу. Что получится - не важно, главное, что
перенос "заглохнет" в пределах первых N разрядов, а старшие так и останутся
единичками. Если такое число честно прибавить к положительному - это будет самое
натуральное вычитание! (Проверьте.) Теперь его можно смело обрезать в любом
месте (за пределами N разрядов) и самый старший бит назначать знаковым.
номера разрядов: N 3210 (с 0 считаем или с 1 - не важно)
было ....00000...01ХХХ...ХХХХ
инвертировали ....11111...10YYY...YYYY Y = ~X
прибавили 1 ....11111...1?ZZZ...ZZZZ
А вот если исходное число было 0, то при инверсии все его битики будут
единичками и при прибавлении единицы перенос убежит куда-то в бесконечность. И в
результате опять получится ноль! Т.е. ноль в этой системе - адын штука. И что
суммирование что вычитание выполняет одна и та же схема (дополненная инвертором
и дополнительно подающая из ниоткуда перенос на крайний разряд).
А еще я как всегда забыл рассказать про признаки результата. Хотя тоже
упоминал их неоднократно. Ну так вот:
N - "негатив" - указывает что результат отрицательный (т.е. старший бит = 1)
Z - "зеро" - указывает что весь результат нулевой
V - признак переполнения - указывает что знаковый разряд "неправильный"
C - перенос в следующий разряд, находящийся за пределами разрядной сетки
Эти самые признаки результата автоматически вырабатываются при выполнении
любой содержательной операции (в том числе и при пересылке данных командой MOV),
а команды управления порядком действий их не трогают. Зато те, которые
"ветвления" используют как условие, когда решают, ветвиться им или нет. Правда
"побитовые" команды (BIT, BIC, BIS и XOR), команды пересылки и еще почему-то
INC и DEC признак переноса тоже сохраняют. За сим некоторые операционные
системы используют его как признак ошибки: если при возврате из системного
вызова признак C сброшен - всё хокей, если установлен, значит что-то не
получилось - смотри коды ошибок.
С признаками N и Z всё очевидно; в C попадает бит, выдвинутый из слова
командой сдвига, а вот что происходит при сложении? (Вычитание, это как мы уже
знаем, - разновидность сложения!) На сколько я понимаю, в признак C попадает
перенос из старшего разряда сумматора за пределы разрядной сетки. А как
устанавливается признак V - непонятно. Везде пишут что в результате
"переполнения". А мол это самое переполнение - когда знаковые разряды были
одинаковые, а результат получился с противоположным знаком. (Что, кстати не
плохо бы было проверить.) Однако несомненно (известно из документации) что если
мы сравниваем два числа вычитая одно из другого: А-Б,
то если А==Б -> Z=1 потому что весь результат получится нулевой
и А!=Б -> Z=0
для чисел без знака:
если А>Б -> C=0 потому что переноса за пределы разрядной сетки не будет
А<=Б -> C=1 результат получится "отрицательный" - перенос выскочит
за пределы разрядной сетки
для чисел со знаком дело обстоит сложнее:
А>Б определяется по комбинации признаков N^V (здесь ^ - искл.ИЛИ)
а для А<=Б эта комбинация должна быть равна нулю (т.е. либо оба нули, либо
оба единички)
Команд ветвления, использующих эти признаки результата - восемь. Устроены
они так: младший байт - расстояние на которое надо перейти, если проверяемое
командой условие выполняется - однобайтовое число (со знаком), указывающее
количество слов, т.е. прибавляющееся к счетчику команд со сдвигом на один бит.
Один бит - состояние проверяемого признака, при котором надо переходить,
остальное - код операции (КОП).
К0КSSS по битам: К 000 кКК ПSS SSS SSS где S - смещение, П - признак, К - КОП.
0 04 BR 0 000 1 - "безусловный" переход - ничего не проверяет
0 1 BNE / BEQ 0 001 * - Z если равно / не равно (нулю)
0 2 BGE / BLT 0 010 * - N^V >=0 / <0 - для чисел со знаком
0 3 BGT / BLE 0 011 * - Z|(N^V) >0 / <=0 - для чисел со знаком
1 0 BPL / BMI 1 000 * - N если плюс / минус
1 1 BHI / BLOS 1 001 * - C&Z >0 / <=0 - для чисел без знака
1 2 BVC / BVS 1 010 * - V нету переполнения / есть
1 3 BCC / BCS 1 011 * - C нету переноса / есть
или BHIS / BLO - C >=0 / <0 - для чисел без знака
Здесь команды перечислены в порядке возрастания кодов операций (а не в
логическом порядке - типа сначала проверка отдельных признаков, потом их
комбинаций для сравнения чисел со знаком и без такового). Обратим внимание: один
бит кода операции (тот, который обозначен маленькой буквой к) неизменно ноль, зато
вместо него задействован старший (знаковый) бит слова. Это к тому, как шло
распределение пространства кодов команд.
r ккк xxx xxx yyy yyy - двухадресная команда - кроме ккк = 000 и 111
из того, что осталось:
1 111 *** *** *** *** - зарезервировано под команды ППЗ
к 000 0кк пss sss sss - команды ветвления К0К*** (кроме к_ккп = 0_000)
* 000 1кк ккк ххх ххх - одноадресные команды, из них мы уже рассмотрели:
r ... .01 ккк ... ... - основные *05К**
r ... .10 0кк ... ... - команды сдвига *06К** (только 4 штуки)
значит пока что незадействованными (или нерассмотренными) остались коды:
* ... .10 1** - 4 штуки *064** - *067**
* ... .11 *** - 8 штук *07***
еще пропущена команда с кодом 00 (с учетом старшего бита - 2 штуки)
* ... .00 ... ... ... *04*** это будут JSR и EMT/TRAP
ну и совершенно еще не использованные
0 111 *** *** *** *** 07****
а так же - то, что осталось от команд ветвления (к_ккп = 0_000)
0 000 000 0** *** *** - т.е. команды с полностью нулевым старшим байтом
А ведь у нас еще совершенно не рассмотрены команды передачи управления.
Четыре блудных команды вида *064** - *067** (с учетом использования под код
операции знакового разряда - 8 штук) я всётаки насобирал как говорится "с бору
по сосенке":
- Команда SXT с кодом 0067ХХ (где ХХ - полноформатный операнд) - "расширение
знака" - заполнение старшего байта слова знаковым разрядом младшего. Т.е.
превращение однобайтового целого числа в двухбайтовое. (Кстати, при загрузке
байта в регистр это происходит автоматически.)
- Пара команд с кодами 1067ХХ и 1064ХХ - MTPS и MFPS - загрузка операнда ХХ в
слово состояния процессора и наоборот - сохранение ССП по указанному адресу.
У старших моделей они отсутствуют, т.к. это была бы "дырка" в механизме защиты.
(Вместо этого есть привилегированная команда SPL для установки уровня приоритета
и обычная команда с кучей разных названий для установки и сброса признаков
результата, имевшаяся впрочем и самых младших моделей.)
- За то у самой старшей модели (где всё по максимуму) есть четыре интересные
команды с кодами 0065ХХ, 1065ХХ, 0066ХХ и 1066ХХ - MFPI, MFPD, MTPI, MTPD,
занимающиеся пересылкой слова данных ХХ из предыдущего адресного пространства
на стэк текущего. И соответственно со стэка в. А две пары потому что адресные
пространства команд и данных там видите-ли раздельные. (Можно сделать, но не
обязательно. Впрочем речь об этом еще впереди.)
- И еще одна заблудившаяся команда MARK с кодом 0064NN, делающая что-то очень
хитрое. Впринципе это возврат из подпрограммы с параметрами (с одновременным
истреблением оных). Вот сейчас мы это и рассмотрим.
Впринципе для передачи управления в любую точку оперативной памяти (а не на
+/-127 слов как у команд ветвления) вполне хватило-бы команды пересылки MOV:
записал адрес точки перехода прямо в регистр R7 и всё. (В идейном потомке PDP-11
микроконтроллере MSP-430 так и сделано.) Эта возможность не исключается и здесь.
Но разработчики архитектуры сделали отдельную команду JMP с кодом 0001АА, в
которой АА, в отличии от команды MOV с кодом 01АА07 не указание того места, где
взять адрес по коему надо перейти (засунув его в R7), а того, где следующая
подлежащая выполнению команда. (Но если АА = 0х, то получается что эта команда
- в регистре Rх, что абсурд. Зачем так сделано - не знаю, разве что по аналогии
с командой передачи управления подпрограмме JSR.
Команда передачи управления подпрограмме JSR - полутораадресная. Её код
004RАА включает так же как и код команды JMP, указание куда передать управление,
а кроме того - номер регистра, где сохранить адрес возврата. Содержимое самого
этого регистра сохраняется на стэке. А командой возврата из подпрограммы RST с
кодом 00020R - восстанавливается обратно. Спрашивается, зачем так сложно? Почему
нельзя, как все люди, сразу сохранить адрес возврата на стэке, уж коли он есть?
(Впрочем, если в качестве этого регистра указать сам счетчик команд - так оно и
происходит.) Это ведь когда стэка еще небыло - адрес возврата вот так сохраняли
в регистре, а сейчас-то зачем? А это даёт дополнительную гибкость. Например
сразу после команды вызова подпрограммы можно поместить некую полезную
информацию для неё (ну хотя бы текстовую строку, которую оная программа должна
вывести на терминал в качестве диагностического сообщения). Теперь ей не надо
выковыривать из стэка адрес где это находится - он уже в регистре;
попользовалась, сдвинув оный регистр-указатель на первое слово после этой,
предназначенной ей информации - там как раз и будет следующая команда, которой
надо возвернуть управление. Очень удобно.
Уже упоминавшаяся хитрая команда MARK с кодом 0064NN служит для "маркировки"
конца списка параметров и/или локальных переменных подпрограммы (NN штук)
завалявшихся на стэке и при возврате из оной подпрограммы подлежащих удалению.
Она помещается на вершину стэка самой подпрограммой, которая обязательно должна
получать управление командой JSR R5, AAAA; и которая для возврата управления
должна употребить команду RST R6. Тогда R7 <- R6, а там как раз и лежит вот эта
самая команда MARK NN, она делает: R6 <- R7+NN*2; R7 <- R5; R5 <- (R6)+.
Еще к командам передачи управления относятся "командные прерывания"
предназначенные прежде всего для обращения к операционной системе (за услугами)
или каким другим системным программам (например к отладчику или некоему
супервизору ввода/вывода). Не в пример писишке каждой из таких команд поставлен
в соответствие один единственный фиксированный вектор прерывания. Который ни при
каких обстоятельствах не используется ни для чего другого. Таковых команд
четыре: EMT и TRAP с кодом 104ХХХ отличаются одним битом (младшим во втором
байте), а весь младший байт отведён под код запроса - на выполнение команды он
никак не влияет. (То есть получившая прерывание системная программа должна
слазить в то место, куда указывает адрес возврата, взять младший байт команды и
отработать ту функцию, на которую он намекает.) Не знаю почему их две - наверно
просто так коды распределились. Однако одни операционные системы (например
RT-11) используют EMT, а другие (например UNIX) - TRAP. Может у кого-то была
мысль сделать под UNIX`ом виртуальную RT-11? Их вектора 030 и 034.
Два других командных прерывания BPT и IOT с векторами 014 и 020 и кодами
00003 и 00004 якобы предназначены для отладчика и подсистемы ввода/вывода.
(Ну типа отладчик организует в отлаживаемой программе точку останова подменяя
одну из её команд на BPT, а как получит по ней управление - незаметно подменяет
её обратно. Ну а с вводом/выводом-то как?)
Следует иметь в виду, что при прерывании в стэке сохраняется не только
счетчик команд, но и слово состояния процессора. А новые загружаются из вектора
прерывания. (Потому-то они и идут через четыре байта - эти самые четыре байта и
загружаются.) Следовательно и возврат из прерывания должен производиться не как
из подпрограммы, а специально на то предназначенной командой. Каковых две:
000002 RTI - просто возврат из прерывания - без всяких затей
000006 RTT - хитрый возврат из прерывания - специально для отладчика
Тут дело вот в чём: в слове состояния процессора есть такой специальный
признак Т. Если он равен нулю - всё как у людей. А вот если его установить в
единицу - сразу же будет прерывание по тому же самому вектору 014 что и по
команде BPT. А установиться в единицу он может например по команде возврата из
прерывания (ну вот такое слово попало в ССП из стэка). А отладчику, желающему
прогнать программу по шагам, надо чтобы не сразу, а только после того, как
выполнится одна команда. Вот для этого он RTT и использует.
Еще к командам управления можно отнести следующие:
000000 HALT - стоп - останавливает процессор (привилегированная!)
000001 WAIT - ждать - висит на этой команде пока не произойдёт прерывание
000005 RESET - сброс - сброс всех внешних устройств в некое исходное
состояние - в старших моделях ясный пень тоже привилегированная, потому как
приводит в состояние изумления не только всякие там принтеры с терминалами (ну
или контроллеры дисководов, ежели они есть), но и такую важную штуку, как
диспетчер памяти. И буде учинить её в защищенном режиме - одному Аллаху известно
к чему это приведёт.
Еще есть такая привилегированная команда SPL с кодом 00023N -
устанавливающая уровень приоритета прерываний N (буде командами MTPS / MFPS
напрямую читающей и пишущей обратно целиком всё слово состояния процессора
воспользоваться нельзя). И всё - кроме этих трёх других "привилегированных"
команд даже в самой старшей модели нет. (А привилегированность их заключается в
том, что в "системном" режиме они успешно выполняются, а в "пользовательском" -
нет - вызывают прерывание. По-моему по вектору 010, по которому вообще
происходит прерывание при попытке выполнить неизвестную данному процессору
команду.)
Ну про что я еще забыл, прежде чем перейти к "расширенным" командам? Ну
конешно про сброс и установку наших любимых признаков NZVC! Команда эта с кодом
00024Х точнее по битам: 0 000 000 010 1yX XXX где y указывает что именно
нужно сделать - установить указанные единичками в битах ХХХХ признаки NZVC (вот
именно в таком порядке), или же наоборот сбросить. Названий у этой команды -
пруд пруди, в том числе NOP (ничего не делать) - это если все ХХХХ нулевые.
000240 NOP 000260 NOP ничего не делать
000241 CLC 000261 SEC сбросить/установить признак C
000242 CLV 000262 SEV --//-- V
000244 CLZ 000264 SEZ --//-- Z
000250 CLN 000270 SEN --//-- N
000257 CCC 000277 SCC сбросить/установить всё
Ну и еще команда SWAB с кодом 0003АА - перестановка байтов в слове. Она вроде
бы тоже не расширенная а из основного набора.
Подводя промежуточный итог, отметим, что базовый набор команд это
необходимый минимум. Он позволяет выполнить любые требуемые действия, но
содержит только самые элементарные операции. Ни умножения с делением ни тем
более операций с плавающей запятой там нет. Их придётся эмулировать.
"Расширенные" команды - полутораадресные: здесь почти все команды (кроме
исключающего ИЛИ, ну и возможно одинарного многобитного сдвига, которые надо бы
включить в основной набор двухадресных команд, да вот к сожалению элементарно
кодов не хватило) используют пару соседних регистров (обозначено R и R'),
отличающихся младшим битом номера. (Т.е. если указан R2, то второй конешно же
будет R3, а вот если указан R3, то R' будет вовсе не R4, а R2!)
070RАА MUL - целочисленное умножение R*A -> (R,R')
071RАА DIV - целочисленное деление (R,R') / A -> R, R' (остаток)
072RАА ASH - арифметический сдвиг на несколько разрядов (6 бит операнда А)
073RАА ASHC - сдвиг на несколько разрядов двух регистров R и R'
074RАА XOR - исключающее ИЛИ (инверсия битов) R^A -> A
Стэковая плавающая арифметика одинарной точности
07500R FADD - сложение
07501R FSUB - вычитание
07502R FMUL - умножение
07503R FDIV - деление
Регистр R - указатель вершины операционного стэка, с которого берутся
(черырёхбайтовые в данном случае) операнды и куда кладётся результат. Как видим,
здесь свободных кодов - полным-полно (еще 60 штук: 07504R - 07577R) - вполне
хватит соорудить полноценный стэковый сопроцессор. Но в эту сторону развитие
архитектуры не пошло. А с реализацией ППЗ и эти команды упразднили.
Команды вида 076ХХХ тоже нигде не задействованы.
077RNN SOB - цикл со счетчиком: счетчик (регистр R) уменьшается на единицу,
и если получился не ноль - производится переход назад на NN слов.
Вот собственно и вся система команд.
Ах да, еще кажется у Э-85 завалялась такая команда MFPT с кодом 000007,
записывающая в R0 некую константу, содержащую код модели процессора в младшем
байте и что-то такое еще - в старшем. Уж и не знаю зачем.
А команды процессора с плавающей запятой мы рассматривать пока не будем: есть
вещи и по-интереснее. Диспетчер памяти например. И вообще структура памяти.
Ну что можно сказать про память? Адресное пространство - линейное (или
"плоское"). Размер адреса равен разрядности процессора и размеру машинного
слова. Причём в виду наличия развитой системы косвенной адресации это
принципиально. То есть увеличить размер адреса в пределах данной архитектуры
невозможно - для этого надо делать другую машину с другой системой команд.
(Что и было сделано: VAX-11.) Впрочем на момент разработки (и следующие десять
лет) объём адресного пространства в 2^16 байт казался прямо-таки необъятным!
Оперативной памяти туда устанавливалось куда как меньше. Но как известно, дашь
коту сала, а ему всё ма-а-ало! И в конце-концов настали времена...
Впрочем еще раньше появились "старшие" модели, заточенные под многозадачный
режим работы. В том числе и в целях отладки "сырого" (свеженаписанного)
программного обеспечения - от которого следует ожидать всяческих пакостей - ибо
в любой программе найдётся хотя бы одна ошибка, причем по закону всемирного
ехидства такая, чтобы нанести максимальный ущерб... И встала проблема как-то
эти самые задачи друг от дружки отделить (А главное - от обеспечивающего их
работу системного программного обеспечения.) Дабы этот ущерб хоть как-то
локализовать и минимизировать. Ну типа рассадить их все, как психов, по
отдельным комнатам с мягкими стенами...
Вот для этой цели и был придуман "диспетчер памяти", реализующий для каждой
задачи своё собственное "виртуальное" адресное пространство. А то, в котором
находится реально существующая память и внешние устройства - стали называть
"физическим". Появилась возможность расширить физическое адресное пространство
сначала до 2^18, потом до 2^22.
Как это сделано? А вот как:
Адресное пространство (пока что всё сплошь физическое) условно делится на
восемь "страниц" или "банков" по 8 Кбайт. (Слово "банк" - это больше к
собственно памяти а не к пространству, где она размещается. Ферритовый куб вот
как раз и был такого размера.) Управляющие регистры внешних устройств адресуются
так же как память и традиционно все размещаются в последнем восьмом банке.
Диспетчер памяти это такое-же как все внешнее устройство. Но сам он "врезан" в
шину адреса сразу после процессора. И занимается тем, что преобразует выданный
процессором "виртуальный" 16-и разрядный адрес в 18-и или 22-х разрядный
"физический". Делает он это следующим образом: делит адрес на номер страницы
(три старших бита) и смещение в ней (остальные 13). Далее, имея восемь
регистров (по числу страниц) берёт содержимое одного из них и складывает со
смещением со сдвигом на шесть бит. В результате чего с одной стороны как раз и
получается 22-х разрядный физический адрес. А с другой - в пространстве адресов,
издаваемых программой, образуется как-бы окошко (размером в 8 Кбайт), через
которое программа, изменяя соответствующий регистр диспетчера, может обозревать
всё четырёх-мегабайтное физическое адресное пространство, передвигая его по
нему с шагом в 2^6=64 байта.
Программы, работающие под суровой многозадачной ОС реального времени RSX-11
так и делают.
Однако, таких наборов регистров в диспетчере два. Какой именно из них
используется - указывает битик "текущего режима работы" в слове состояния
процессора. Точнее - два битика, но оба они либо 00 либо 11. Состояние 00
считается "привилегированным" - оно предназначено для операционной системы и в
нём честно выполняются абсолютно все команды, реализованные в данном процессоре.
А состояние 11 - соответственно "непривилегированное" - в нём некоторые
"привилегированные" команды ("стоп", "сброс внешних устройств" и еще парочку)
процессор выполнять отказывается - учиняет прерывание по вектору 10, как будто
таковых команд у него нет.
Подобным образом, т.е. вводя два или даже больше режимов работы, один из
которых привилегированный и в нём можно всё, а в остальных выполнение некоторых
действий запрещено - поступают во всех вычислительных системах, ориентированных
на работу под управлением операционной системы. В нелёгкой памяти ЕС-ЭВМ (ряд
"больших" машин с архитектурой содранной с IBM-360) таковых режимов аж
шестнадцать штук. Т.е под указание режима работы (он же - код выполняющейся в
данный момент задачи) задействовано четыре бита (тоже находящихся в ССП), из
которых привилегированной комбинацией является 0000. На эти биты завязана
"ключевая" защита памяти: всё адресное пространство разделено на страницы
размером по два килобайта (т.е. 2^12, а всё адресное пространство у неё - 2^24),
каждой из которых сопоставлен "ключ защиты". (Доступ к нему - парой
привилегированных команд, каковых в этой архитектуре пруд-пруди.) При любом
обращении к странице он аппаратно сравнивается с кодом текущего режима, и буде
не совпадает (и режим не 0000) - вместо обращения учиняется прерывание: типа
задача лезет не в свою память.
Как я уже говорил, у PDP-11 для каждого из двух режимов работы - свой
комплект регистров диспетчера памяти. Т.е. одновременно реализуются два
адресных пространства - одно для операционной системы, а другое - для текущей,
выполняющейся под нею задачи. Но только одной - при переключении задач эти
регистры придётся перенастраивать.
Ну так вот: некая программа, загрузившись в память первой - когда этот самый
диспетчер еще отключен (пропускает через себя адреса без всякого изменения)
настраивает один комплект (тот, который для себя) так, чтобы и вектора
прерывания и управляющие регистры внешних устройств были по-прежнему доступны.
А второй комплект - чтобы нет. Чтобы "окошко" восьмой страницы глядело не туда
где регистры устройств, а туда, где оперативная память. (Или вообще отключить
его нафиг: - к этим восьми адресным регистрам диспетчера прилагаются еще восемь
управляющих, позволяющих задавать размер окошка и его положение (т.е. в начале
оно страницы или в конце) и вообще учинять всякие чудеса.) Далее эта программа
грузит в то место памяти, на которое указывают регистры второго
(непривилегированного) комплекта, некую другую программу. После чего передаёт
ей управление учиняя следующий финт ушами: на вершину стэка кладёт стартовый
адрес оной программы и после него - слово, которое попадёт в ССП. В нём
указывается код режима = 11. После чего она выполняет возврат из прерывания -
команду RTI. (Но перед этим, разумеется, не забывает включить диспетчер
памяти.) Всё. Теперь работает вот эта вторая невесть куда загруженная программа,
но никакие внешние устройства ей недоступны. В том числе и регистры диспетчера
памяти. А т.к. её запустили как "непривилегированную", то и отключить диспетчер,
выполнив команду RESET (сброс внешних устройств) ей не удастся. (А если бы даже
и удалось, ничего хорошего из этого не получится: она же сидит невесть по каким
адресам...) И обратиться ко второй, запустившей её программе она (если та не
дура и изолировала её от себя полностью) может только устраивая командные
прерывания. (Для чего они собственно и нужны.) А еще первая программа, время от
времени получая управление по прерыванию от таймера, может через некоторое
время совсем отобрать управление у второй программы и выгрузив её на диск (или
оставив как есть - если памяти хватает) загрузить еще одну - третью программу,
и дать поработать ей...
Вот так все многозадачные операционные системы и работают. В т.ч. и UNIX.
Обычное распределение памяти, принятое например в RT-11, таково: с нулевого
адреса находятся вектора прерывания; далее, сразу вслед за ними располагается
системный стэк. (Чтобы он не налез на вектора - у старших моделей есть
специальный регистр контроля границы стэка: как содержимое R6 становится меньше
чем в нём указано - сразу прерывание.) После этого располагается код программы.
Сама операционная система помещается в самый конец оперативной памяти. Место
между нею и программой - свободно; туда по мере надобности подгружаются драйвера
устройств, или там-же может быть запущена вторая программа "фонового режима",
скомпилированная таким образом чтобы быть "перемещаемой" (т.е. все используемы в
ней адреса - относительные; в результате ей совершенно без разницы с какого
места оперативной памяти её загрузят). Она получает управление, когда программа
переднего плана чего нибудь ожидает. Например обратилась к операционной системе
за очередным байтом, введенным с терминала, а сидящий за ним пользователь ни
мычит - ни телится...
А вот UNIX придерживается другой политики. Он, как известно, размещает каждую
выполняемую программу в персональном, исключительно для неё предназначенном
адресном пространстве, в которое ни вектора прерывания ни управляющие регистры
внешних устройств не отображаются - только исключительно оперативная память. Ну
так он грузит туда содержимое выполняемого файла прямо с нулевого адреса, а стэк
помещает в самом конце адресного пространства. Место между вершиной стэка и
концом области памяти куда загружен код из файла - свободно. Под него даже
память не выделяется. (Регистры диспетчера памяти настраиваются так, что
обращение по этим адресам вызывает ошибку - прерывание по специально для этого
предназначенному вектору 250.) При переполнении стэка (что отслеживается с
помощью регистра его верхней границы) память под стэк добавляется автоматически.
А вот нижнюю границу доступной ей памяти программа может сдвинуть как вверх так
и вниз вполне сознательно - обратившись для этого к операционной системе. В
этом месте размещаются данные, количество которых на момент написания программы
неизвестно. (Как правило организованные в виде т.н. "кучи".)
Самая-самая старшая модель (та, которая у нас Э-79) имела не два а три
комплекта регистров диспетчера и соответственно три режима работы разной
привилегированности. А еще - второй комплект регистров общего назначения
процессора. Видимо чтобы при прерывании не тратить время на сохранение их
содержимого и перед выходом - на восстановление их обратно. Какой комплект
включен - указано в ССП, а оно загружается из второго слова вектора прерывания.
(Т.е. даже и делать ничего не надо.) А еще каждый комплект регистров диспетчера
памяти в двух экземплярах: один используется когда процессор лезет за командой,
а второй - когда за данными. То есть можно было включить такой режим... но все
обычно воздерживались. Ну и еще, чтобы не шарить окошком по физическому
адресному пространству (очень уж это громоздко, особенно когда всего-то и надо,
что прочитать номер системного вызова и к нему пару параметров) - вот те самые
команды обращения в "предыдущее" адресное пространство, номер которого хранится
вместе с кодом текущего в слове состояния процессора. Но по-моему эти четыре
команды тоже были привилегированные, а то ТАКАЯ дырища в защите памяти
получается...
В общем сплошной гигантизм.
=========================================================================
Приведённые выше (и ниже) описания систм команд к сожалению не полные:
как минимум для каждой из команд надо указать как она устанавливает признаки
результата в слове состояния, а я только описал это словесно, причем довольно
поверхностно.
Ну и конешно практически нету никаких описаний внешних устройств, так - по
одной штуке, чисто для примера.
Однако, я в общем то и не ставил себе целью написать справочник - люди пишут
об этом толстые серьёзные книжки... Я же всего лишь собирался немножко
проиллюстрировать (на конкретных примерах) как вообще может быть организована
система команд...
=========================================================================
Идейный потомок PDP-11 - семейство микроконтроллеров MSP-430.
Как это выглядит.
Как микросхема о двадцати или о шестидесяти четырёх или даже о ста ногах.
(Они разные бывают - их же целое семейство.) Вся вычислительная система на одном
кристалле: и процессор и ОЗУ и ПЗУ с программой (электрически
перепрограмируемое) и некоторое количество устройств: от полутора портов
ввода/вывода и одного таймера в самых простейших, до ЦАП`ов и АЦП или даже
устройства управления Ж-К индикатором в больших и навороченных. (Но USB с
Изернетами не замечено - не для того этот контроллер предназначен.) А тактовых
генераторов у него на борту аж три штуки. Только кварц подключай!
Тактовая частота по нынешним временам невысокая - до 8 МГц максимум. (Новые
пошли - там до 16.) А минимальная - любая (до 0 герц включительно). Но команду
типа регистр-регистр выполняет за один такт, регистр-память - за 2-3 такта, а
память-память - за 5 или 6. Тоесть максимум восемь миллионов регистровых
операций в секунду. Для сравнения - у самой старшей из PDP-11 было только три
(при такой же разрядности и сопоставимой системе команд - а это куда важнее
"численных" значений).
Питается эта штучка от трёх вольт - на батарейку рассчитано. Точнее от 3.7 -
самая свежая литиевая, до 1.9 - самая самая разряженная пара обычных
угольно-цинковых. И даже при 1.5 вольтах вроде-бы даже немножко работает. К
тому-же малопотребляющая: если раскочегарить на самую большую тактовую частоту
- потребление будет милиампер 35 - 40. А в самом "дежурном" режиме, когда
только часы тикают - порядка 3 или даже 1 микроампера. (У батареек саморазряд и
то больше бывает.) Шаг между ногами мелкий (порядка пол миллиметра), впрочем как
у большинства современных буржуйских микросхем - вручную припаять впринципе
можно, но трудно, а уж плату под неё нарисовать...
Как организована.
Примерно так же как и PDP-11, только у неё всё наоборот: управляющие
регистры внешних устройств расположены с нулевых адресов, а таблица векторов
прерываний - в самых старших. Из регистров процессора, счетчик команд тоже
самый первый - тот который R0. А R1 соответственно - указатель стэка.
Итак, машинка тоже со всех сторон 16-и разрядная; адресное пространство тоже
линейное, размером 2^16=64K и тоже адресуется с точностью до байта. В самом
начале размещаются регистры устройств (какие есть); потом идёт оперативная
память, как правило очень немного (например 2 Кбайта), а в конце адресного
пространства ЭППЗУ ("флеш-память") разного размера (от 2 до 60 Кбайт), в самом
самом конце которой - таблица векторов из 16 слов. Самый последний из векторов -
стартовый адрес программы - по нему передаётся управление сразу после включения
питания. Т.е. сразу после подачи питания устраивается сброс - вот по сбросу
содержимое этого вектора и записывается в счетчик команд.
Да кстати - в отличии от PDP-11 вектор здесь - одно слово - то, что
помещается в счетчик команд. Слово состояния (которое здесь разумеется тоже
есть) при прерывании просто обнуляется.
Машинка изначально задумана как контроллер: вектора прерывания размещаются
в ПЗУ, которое далеко не во всех моделях перепрограммируемое. Тоесть
предполагается что в период эксплуатации программа изменяться не будет.
Между ОЗУ и ЭППЗУ есть еще кусочек просто ПЗУ (1 Кбайт), где еще при
изготовлении прописана программа-загрузчик, с помощью которой это самое ЭППЗУ и
программируется. Эта программа получает управление в случае учинения сброса не
обычным а неким особо хитрым образом (что должен проделать внешний загрузчик),
после чего общаясь с внешним загрузчиком по последовательной линии связи (типа
ком-порта) может стереть одну страницу или всё ЭППЗУ, записать туда нечто
переданное ей снаружи, но так-же и считать и передать наружу что сейчас в памяти
находится. Только она требует пароль, в качестве которого ей надо предъявить
содержимое таблицы векторов. (Ну типа кто прошлый раз туда всё это писал -
должен знать.) А без этого - только стирает всё ЭППЗУ, после чего резко добреет
и уже дозволяет делать что угодно.
Но это так сказать "аварийный механизм" - обычно программируют этот
контроллер с помощью т.н. интерфейса JTAG, каковой позволяет так-же лазить в
регистры процессора и внешних устройств; запускать и останавливать программу,
прогонять её по шагам... Но у него есть плавкий предохранитель - его можно
пережечь и тогда JTAG блокируется. (Типа всё - отладка закончена.)
А вообще перепрошить ЭППЗУ может любая программа, а вовсе не только
специально на то уполномоченная. Среди устройств у любого, даже самого младшего
микроконтроллера этого семейства обязательно есть устройство управления
флеш-памятью. С помощью которого можно стирать страницы ЭППЗУ и/или писать туда
что угодно... Ежели конешно питающее напряжение два вольта или больше... Ну и
аккуратно надо это делать - буквально ходить на ципочках: все прерывания
запретить, в ЭППЗУ зря не лазить - код перенести куда нибудь в другое место
(да хоть в ОЗУ)... В начале ЭППЗУ есть две маленькие странички - специально
чтобы туда данные можно было писать - ну те, которые должны сохраняться после
отключения питания. А так - страницы ЭППЗУ довольно большие.
Напомню, что ЭППЗУ флеш-типа пишется пословно, а вот стирается - только
сразу вся страница. При стирании все биты сбрасываются в "0", а при записи
некоторые из них устанавливаются в "1".
Аналог системной магистрали, к которой в PDP-11 подключены все входящие в
систему устройства, в MSP-430 если и есть, то не афишируется. По крайней мере
наружу никакой магистрали для подключения внешней памяти или еще чего-то
подобного (как у других аналогичных контроллеров) не выходит. Система полностью
закрытая.
Как организованы прерывания - тоже неизвестно, но внешне всё выглядит почти
как в PDP-11: одно устройство - один вектор; вектор - одно слово - адрес
подпрограммы, которая должна обслуживать данное устройство. В слове состояния
процессора 1 бит - разрешение прерываний. Если он ноль - ни на какие требования
прерываний процессор не реагирует. У каждого устройства где нибудь в самом
главном управляющем регистре есть два бита: признак готовности - указывающий что
надо бы это устройство обслужить и разрешение прерывания, разрешающее ему в
случае готовности это самое прерывание требовать. (По фиксированному вектору,
разумеется.) Ещё там как правило есть битик, включающий это устройство (ну или
выключающий нафиг - чтобы лепестричесво не потребляло). Ежели устройство
включено, готово и прерывание ему требовать разрешено - требует. Ежели в ССП
процессора прерывание тоже разрешено - оно и происходит. Но признак готовности
как правило сам не сбрасывается - его должна сбросить запущенная по прерыванию
программа. А то после выхода устройство потребует прерывания заново. Хотя
устройства сильно разные...
Например у сильно вумного 12-и разрядного АЦП есть кольцевой буфер для
результатов преобразования, состоящий из 16 ячеек. И к каждой ячейке - признак,
указывающий что АЦП, запскающееся само собою - от таймера, в эту ячейку уже
чего-то положило, а процессор это оттуда еще не взял. Т.е. сбрасывающийся
автоматически - при считывании из ячейки. Ну так АЦП (если разрешено) требует
прерывания если хоть один из этих признаков установлен. А разрешение - для
каждого признака персонально. (Так, чтобы зря не дергать программу - можно
например разрешить только каждый четвертый и забирать за раз по четыре слова.)
Процессор - типичная регистровая машина, как и PDP-11, только регистров
здесь не 8 а 16. Первые четыре используются для специальных целей, остальные -
самые обычные.
Как уже говорилось: R0 - счетчик команд, R1 - указатель стэка. А вот через
R2 доступно слово состояния программы (ССП). Кроме всё тех же самых признаков
NZVC (правда несколько в другом порядке) и признака разрешения прерывания, ССП
содержит четыре бита, напрямую управляющих тактовыми генераторами, а так же
подачей тактовых импульсов на ЦП и на периферийные устройства. Команды СТОП у
этой машины нет - за ненадобностью: установила программа в единичку
соответствующий бит (управляющий подачей тактовых импульсов на процессор) -
процессор остановился. А коли прохождение их на периферийные устройства не
запретила - таймер продолжает тикать. Как дотикает - потребует прерывания. При
прерывании ССП обнулилось (после сохранения его старого содержимого в стэке) -
прохождение тактовых импульсов на процессор разрешилось - программа пошла. Как
дошла до возврата из прерывания - в R2 записалось из стэка то что там раньше
было. А там в соответствующем бите единичка - тактовые импульсы на процессор
не поступают, программа опять стоит...
А регистр R3 - фиктивный. Его как такового просто нет... Впрочем не будем
забегать вперёд.
Самое интересное - это несомненно система команд. Здесь она настолько
маленькая и простая, что у меня даже слов нет. Так как здесь всё по шестнадцать,
то естественная для данной машинки система счисления - шестнадцатеричная.
Соответственно команда разбита на группы по четыре бита, каждая из которых
обозначается одной шестнадцатеричной цифрой. (Кто вдруг не знает: первые десять
цифр - обычные десятичные, а недостающие шесть - латинские буквы A..F.)
Ну так вот: команда, как и у PDP-11 занимает шестнадцатиразрядное слово.
Система команд тоже двухадресная; адресация операндов тоже через регистры,
только методов адресации куда как поменьше. (Зато регистров в два раза больше.)
2-х адресная команда (по битам) выглядит так: КККК АААА браа ББББ,
где: номера битов 15... ...8 7... ...0
КККК - код команды байты старший младший
АААА - первый операнд - "источник"
ББББ - второй операнд - "приёмник" - в него записывается результат
б - метод адресации для операнда Б - всего один бит (увы) - Rx или E(Rx)
аа - метод адресации для операнда А - тоже (увы) всего два бита
р - размер операнда - если 0 - целое слово, если 1 - байт.
Таким образом для операнда-приёмника всего два метода адресации: либо
регистровый, либо индексный. Для операнда-источника - чуть побольше (но тоже
никаких особо косвенных): 00 - Rx, 01 - E(Rx), 10 - (Rx) и 11 - (Rx)+.
Индекс Е, как и в PDP-11 - второе или третье слово команды.
А вот на второй (ССП) и третий (фиктивный) регистры эти методы адресации
действуют особым образом: совершенно очевидно, что лазить непосредственно в ССП
имеет смысл (ну например напрямую установить или сбросить какие хочешь признаки -
никаких особых команд для этого не надо!), а вот использовать его содержимое в
качестве адреса - абсурд. За сим сделано так:
- регистровый метод адресации по отношению к R2 работает как обычно
- а вот индексный считает что там - ноль, т.е. получается абсолютная адресация.
Все остальные методы адресации применительно к R2 и к R3 дают "литералы" -
короткие константы (используемые в программе наиболее часто): 0, 1, 2, 4, 8, -1.
R3 -> #0
E(R3) -> #1 - этой команде второго слова не причитается
(R3) -> #2 (R2) -> #4
(R3)+ -> #-1 (R2)+ -> #8
А все остальные константы, как и в PDP-11, получаются с помощью известного
программного фокуса-покуса (R0)+ (или, что то же самое @R0+).
Сразу обратим внимание: с одной стороны существенно более бедный по сравнению
с ПДП-11 набор методов адресации заставляет использовать несколько команд там,
где в той же ПДП-11 хватало одной. В том числе чтобы обратиться по адресу,
лежащему в оперативной памяти, надо сперва затащить его в регистр. (Благо
регистров здесь в два раза больше.) Но с другой стороны это же обстоятельство
позволило расширить адресное пространство, просто увеличив размеры регистров.
В то время, как в ПДП-11 подобный фокус невозможен как раз из-за наличия сильно
косвенных методов адресации.
Двухадресных команд - 12 штук (коды 0..3 - для расширения системы команд)
4 0100 MOV A,B - пересылка A -> B или B=A
5 0101 ADD A,B - сложение A+B -> B или B+=A
6 0110 ADDC A,B - сложение с переносом A+B+C -> B или B+=A+C
7 0111 SUBC A,B - вычитание с переносом ~A+B+C -> B или B-=A+C
8 1000 SUB A,B - просто вычитание ~A+B+1 -> B или B-=A
9 1001 CMP A,B - сравнение (B-A)?
A 1010 DADD A,B - сложение с 10-ым дополнением [A+B+C] -> B ???
B 1011 BIT A,B - сравнение побитовое (A&B)?
C 1100 BIC A,B - сброс битов (НЕ-И) ~A&B -> B или B&=~A
D 1101 BIS A,B - установка битов (ИЛИ) A|B -> B или B|= A
E 1110 XOR A,B - инверсия битов (искл.ИЛИ) A^B -> B или B^= A
F 1111 AND A,B - просто побитовое (И) A&B -> B или B&= A
Как видим, кодов хватило на всё, даже на десятичное сложение (которое лично я
ни разу в жизни так ни для чего и не использовал). Но команд, не вписывающихся в
концепцию "операция за один такт", таких как умножение и деление, здесь нет.
Заметим: в команде CMP операнды вычитаются так же, как и в команде SUB,
разве что результат нигде не сохраняется. В аналогичной команде ПДП-11 порядок
вычитания операндов - обратный.
Для умножения в тех моделях, которые по-старше, предусмотрено отдельное
устройство - аппаратный умножитель: в один регистр засовываешь первый операнд, в
другой - второй, и через пару тактов можно считывать 32-х разрядный результат.
Причем это устройство выполняет не просто умножение, а (при желании) еще и
умножение с накоплением - операция, весьма востребованная во многих алгоритмах
(например при цифровой фильтрации) - перемножить множество пар чисел и все
результаты сложить.
А вот деление - увы - так и приходится при необходимости делать вручную. Про
операции с плавающий запятой никто даже и не заикается - в тех областях
применения под коие заточен сей контроллер, нужны они как собаке пятая нога.
(Кошек за хвост на бегу хватать.)
Под все остальные команды выделены коды 0..3, но задействованы не все:
Кодов команд с первой цифрой 0 в базовом наборе нет (оставлены для расширения);
Одноадресные команды - цифра 1 (но два бита в коде операции незадействованы);
команды ветвления - цифры 2 и 3, точнее первые три бита 001, следующие три - код
операции, и оставшиеся десять - смещение со знаком (расстояние, на которое надо
перейти).
Одноадресных команд - 8 штук. Их коды (по битам): 0001 --КК Крaa АААА,
где: номера битов 15... ...8 7... ...0
-- - незадействованные биты (оставлены для расширения, должны быть = 0)
ККК - код операции
АААА - номер регистра для единственного операнда
aa - его метод адресации
р - его размер (0 - слово, 1 - байт)
ККК
000 RRC A - циклический сдвиг вправо (такой же в точности как в PDP-11)
001 SWPB A - обмен байтов в слове
010 RRA A - арифметический сдвиг вправо (такой же в точности как в PDP-11)
011 SXT A - расширение знака ( --//-- )
100 PUSH A - положить на стэк A -> -(R1)
101 CALL A - переход к подпрограмме R0 -> -(R1); A -> R0
110 RETI - возврат из прерывания (R1)+ -> R2; (R1)+ -> R0
111 NOP - нет операциии
Так как декрементного метода адресации нет - пришлось вводить отдельную
команду PUSH чтобы положить что либо на стэк; особой команды POP не требуется:
эту функцию успешно выполняет MOV (R1)+, XXX. Она же (в форме MOV (R1)+,R0)
заменяет команду возврата из подпрограммы. А вот возврат из прерывания, где
приходится вытаскивать из стэка два слова - отдельная команда. Сдвиги - только
вправо (к младшим разрядам), потому что функции сдвигов влево (к старшим)
успешно выполняет команда ADDC A,A, или для арифметического сдвига, при котором
бит C задвигать в младший разряд не надо - просто ADD. Правда с сохранением
знакового разряда некоторые проблемы - но ничего не поделаешь - нет в мире
совершенства.
Признаки результата NZVC располагаются в ССП (он же R2) не совсем как в
PDP-11: ---- ---- П--Т NZVC в младшей модели, где только 1 бит приоритета П
---- ---V **** ПNZC здесь **** - биты управляющие тактовыми сигналами
Т.е. не самый нужный признак переполнения V вынесен в старший байт - для
того, чтобы в число первых четырёх попал признак П - разрешения прерывания.
(Напомню, что короткие константы (литералы) у нас только до восьми, т.е.
позволяют достать только четыре младших бита.) И еще, в отличии от PDP-11 -
команды MOV, BIC и BIS признаков не изменяют - чтобы можно было использовать
их для изменения этих же признаков напрямую (обращением прямо в R2) не опасаясь
неожиданных последствий.
Используются признаки - в точности так-же как и в PDP-11 - командами
ветвления, каковых здесь восемь штук. (В PDP-11 было 7 пар условных
переходов и один безусловный - всего пятнадцать.)
Команда ветвления по битам выглядит так: 001К ККSS SSSS SSSS,
где: номера битов 15... ...8 7... ...0
SS...SS - смещение (кол-во слов, на которые надо перейти) число со знаком
ККК - код команды:
000 JNZ или JNE ветвление если Z==0 т.е. если А!=Б
001 JZ или JEQ Z==1 А==Б
010 JHS или JNC C==0 А< Б для чисел без знака
011 JLO JC C==1 А>=Б --//--
100 JN N==1 если число отрицательное
101 JGE (N^V)==0 А>=Б для чисел со знаком
110 JL (N^V)==1 А< Б --//--
111 JMP безусловный переход
Как видим: команд ветвления куда как по-меньше, т.е. в наличии действительно
только самые необходимые. Например команд в чистом виде проверяющих состояние
признака V - нету - его, если очень припечет, придётся проверять на общих
основаниях (например командой BIT #0x100) и ветвиться уже по признаку Z.
Команда JMP здесь - полный аналог команды BR у PDP-11, а аналога ейной команды
JMP нету за ненадобностью - эту функцию успешно выполняет команда MOV XXX,R0.
Ну-с, можно подвести некоторый итог: 12 двухадресных команд, 8 одноадресных и
8 команд ветвления - всё, других команд у этой машины просто нет!
Внешних устройств у этой машины довольно много, все разные. Выделить
"типичное" пожалуй что не представляется возможным... Хотя самое простое, это
пожалуй порт ввода/вывода.
Этот самый порт - всего лишь восемь ног микросхемы - процессор может ими
управлять по своему усмотрению. Таких портов у контроллера может быть всего два
(причем один - неполный), а может быть и шесть и даже больше...
Типичный порт представлен группой из нескольких однобайтовых регистров,
каждый из битов которых соответствуют одному выводу (ноге) контролера.
- регистр направления - "1" - нога работает на выход, "0" - это будет вход
- регистр ввода - какое значение на ноге, такое и считывается из данного бита
- регистр вывода - только если на выход - что сюда записать то и выводится.
- регистр передачи ноги какому либо устройству - если здесь "1" то состояние и
функции данного вывода микросхемы будет определять по своему усмотрению некое
другое связанное с этой ногой устройство (таймер например), а не вышеописанные
три регистра.
- регистр подтяжки - (в новых моделях) подключает резистор, подтягивающий эту
ногу к питанию.
Некоторые порты умеют требовать прерывание. Для них дополнительно:
- регистр флагов прерывания - если "1" - будет требовать (если разрешено)
- регистр разрешения прерывания - разрешает требовать (если "1")
- регистр фронта - определяет по какому фронту на входе (переднему или заднему)
устанавливается флаг прерывания. Следует заметить, что этот механизм работает
совершенно автономно: если прерывание от данной ноги запрещено, при приходе
указанного этим регистром фронта флаг (в регистре флагов) всё равно
устанавливается. И может быть использован для чего-то полезного. А сбрасывать
флаги так и так надо "вручную" (сами они только устанавливаются).
Например подключим к этому порту коллекцию кнопок, сбросим все флаги и
разрешим прерывания. Как прерывание произошло - значит кнопку нажали. Теперь
надо дождаться окончания дребезга: механические контакты имеют такую нехорошую
особенность - замкнул один раз, а она выдала с десяток импульсов... Флаг
сбрасываем, прерывание запрещаем, запускаем таймер. Как таймерный интервал
истёк - смотрим, были ли еще фронты? Если да - значит дребезг еще не кончился,
запускаем таймер по-новой. (А интервал берём в разы больше характерного
интервала дребезга.) Если небыло - меняем фронт на противоположный (чтобы теперь
зафиксировать отпускание) и по-новой разрешаем прерывание...
=========================================================================
Рассмотрим, как организовано расширение адресного пространства в МСП-430.
Расширение до мегабайта (2^20), тоесть теперь адрес стал на 4 бита длиннее
слова, с которым работает процессор. Что для этого сделали:
Во-первых - на четыре бита расширили регистры процессора. (Kроме R2,
который содержит слово состояния процессора (ССП) и соответственно адресом
быть никак не может.) Расширили так же конешно и арифметико-логическое
устройство (АЛУ) и внутренние магистрали процессора... Только снаружи этого
не видно. В результате теперь стало не два вида операндов (байт и слово),
а три: еще и 20-и разрядное значение. Для его обозначения в мнемонике
ассемблерных команд стали использовать буковку А.
Во-вторых - чтобы приспособить уже имеющиеся команды для работы как с
такими вот 20-и разрядными значениями, так и с "дальними", тоже 20-и разрядными
адресами, ввели "префикс" - такую специальную команду, которая сама ничего не
делает, а только указывает следующей после неё обычной команде что она теперь
будет "расширенная". В ассемблерной мнемонике наличие такого префикса
обозначается добавлением после команды буковки X (например не mov, а movx).
Префикс содержит по четыре дополнительных бита к индексу каждого из
операндов, если конешно их методы адресации - индексные. Или к
"непосредственному" операнду - если метод адресации операнда-источника @r0+.
А так же дополнительный бит к имеющемуся в команде признаку размера операнда.
Выглядит это так:
биты 15... ...8 7... ...0
префикс: 0001 1aaa alxx bbbb где aaaa и bbbb - старшие 4 бита
для сравнения ** к операндам AAAA и BBBB
1о-адр.команда: 0001 00kk kraa AAAA (kkk = 0..7) - код операции
2х-адр.команда: KKKK AAAA braa BBBB (KKKK = 4..F) - код операции
Бит l префикса - дополнение к биту r команды, указывающему размер операнда:
при l=1 бит r как и раньше обозначает слово (r=0) или байт (r=1)
при l=0 бит r=1 обозначает 20-и битный операнд, а 00 - зарезервировано.
А биты xx тоже зарезервированы и должны быть 00.
В ассемблере для данного процессора (в отличии, например, от ассемблера
ПДП-11) принято буковку, указывающую размер операнда, писать через точку.
Например mov.w и просто mov - это пересылка слова, а mov.b - пересылка байта.
(По мне - так нагляднее.) Так что теперь эта коллекция обогатилась суффиксом
".a". Но написать mov.a будет неправильно: без префикса сей размер не укажешь!
Надо "movx.a".
Однако, если команда типа регистр-регистр, то оставшиеся не у дел биты
aaaa и bbbb используются по-другому: bbbb указывает сколько раз в дополнение
к первому нужно повторить эту команду. Прямо или косвенно - в зависимости от
младшего из битов aaaa: если он "1" то bbbb это номер регистра, а не сам
счетчик повторений. (В регистре тоже используются только четыре младших бита.)
Еще один из битов aaaa (если "1") велит считать значение признака переноса C
равным нулю. (Что имеет смысл только для команды побитового сдвига, ну и еще
DADD.) А два старших бита поля aaaa так и остались незадействованными.
В третьих - проблема сохранения 20-и разрядных адресов в памяти. И в
частности в стэке при обращении к подпрограмме и при прерывании.
При прервыании, как известно, в стэке сохраняются два слова. Второе - слово
состояния процессора, старший байт которого почти пустой. Вот лишние четыре
бита счетчика команд туда и запихнули. И сохраняются
они теперь всегда.
А вот для подпрограмм так и пришлось вводить "дальний"
вызов сохраняющий оные четыре бита во втором слове. И соответствующую команду
CALLA. Для чего пришлось несколько перекроить две последние одноадресные
команды RETI - возврат из прерывания и NOP - нет операции. (Теперь говорят,
что она якобы была резервная, а в качестве NOP велено использовать MOV #0,R3.)
Они обе отличаются тем, что состоят только из кода операции, а отведенные под
операнд семь бит у них содержат нули. Вот один из этих битов - самый старший,
(бит 6) и заняли под код операции:
биты 15... .. .8. 6 5....0
1о-адр.команды вообще: 0001 -- kkk r aaAAAA kkk - код операции = 0..7
команда reti 0001 00 110 0 ------
команда calla 0001 00 110 1 aaAAAA
команда calla 0001 00 111 0 bbBBBB
команда nop 0001 00 111 1 ------
Таким образом команд CALLA - две. У первой из них операнд aaAAAA используется
традиционым образом: AAAA - номер регистра, aa - метод адресации к нему.
Методы адресации - такие же как и во всех прочих командах. Однако, если при
регистровом в счетчик команд заносится 20-битное содержимое указанного
регистра, а при индексном берется из расположенной в памяти таблицы, на начало
которой указывает используемый регистр (и при этом 16-и битный индекс
представляется даже избыточным), то при "непосредственной" адресации @R0+,
когда в счетчик команд заносится константа, идущая за командой следующим
словом - она может быть только 16-и разрядная. Иначе нарушится механика выборки
команд. (Для всех других регистров - то же самое: и индексный и
косвенно-регистровый метод грузят в счетчик команд 20-и разрядный адрес, а
автоинкрементый - только 16-и разрядный.) Поэтому и понадобилась еще одна
команда - у неё BBBB - старшие четыре бита следующей за командой константы,
а методы адресации - свои собственные:
bb = 00 - абсолютный - 20-разрядная константа заносится в R0
01 - относительный - --//-- прибавляется к R0
1* - зарезервировано
И наконец в-четвертых под шумок ввели дополнительные команды, прямо не
связанные с расширением адресного пространства. Работающие со значениями в
регистрах, в том числе 20-и разрядными. Это пересылки - MOVA; сохранение
в стеке (и извлечение обратно) группы регистров за один раз - PUSHM/POPM;
арифметические операции ADDA/SUBA/CMPA; и сдвиги (в основном вправо) R**M.
Рассмотрим, как именно они вписаны в пространство команд.
Вспомним, что распределение этого простнаства началось с 2-х адресных
команд, у которых код команды KKKK принимает значения от 4 до 15
биты 15... ...8 7... ...0
2х-адр.команда: KKKK AAAA braa BBBB KKKK = 4..F
1о-адр.команда: 0001 ХХкк кraa AAAA KKKK = 1 ккк = 0..7
команда ветвления: 001k kkss ssss ssss KKKK = 2,3 kkk = 0..7
Таким образом остались незадействованными команды с KKKK = 0 и в одноадресной
команде - два резервных бита ХХ, которые оба должны были быть равны нулю.
Как было указано выше, под префикс расширенной команды заняли ХХ = 1*,
следовательно осталось еще ХХ=01 - это теперь команды PUSHM/POPM.
биты 15... ...8 7... ...0
1о-адр.команда: 0001 00кк кraa AAAA
префикс: 0001 1aaa alxx bbbb
PUSHM/POPM 0001 01pq NNNN RRRR где p=0 - PUSHM, p=1 - POPM
бит q указывает размер (q=0 - 20 бит в двух словах, q=1 - 16 бит в одном)
RRRR - номер регистра - первого из сохраняемых, последнего из
восстанавливаемых а NNNN - их количество. (0000 -> 1 штука)
Команды с KKKK = 0 распадаются на две группы - по значению 7-го бита
и почти все (кроме сдвигов) работают только с 20-и разрядными значениями
биты 15... ...8 7... ...0
0000 AAAA 1pкк BBBB все команды либо Ra->Rb либо #A20->Rb
пересылка 00 mova здесь BBBB номер регистра-приемника Rb,
сравнение 01 cmpa AAAA при р=1 - регистра-источника Ra,
сложение 10 adda а при р=0 - старшие биты 20-разрядной
вычитание 11 suba константы A20
0000 AAAA 0kkk BBBB - почти все команды - MOVA
000 @Ra -> Rb
001 @Ra+ -> Rb
010 &A20 -> Rb
011 E16(Ra) -> Rb
-----сдвиги 10*
Ѓ 110 Ra -> &B20
Ѓ 111 Ra -> E16(Rb)
Ѓ
L-> 0000 NNkk 010r AAAA - команды сдвига операнда в регистре AAAA
rrcm 00 - циклический сдвиг вправо (через бит переноса)
rram 01 - арифметический вправо (знак - размножается)
rlam 10 - арифметический влево (справа вдвигается ноль)
rrum 11 - беззнаковый вправо (слева вдвигается ноль)
Здесь NN - количество разрядов, на которые сдвинуть операнд (1..4)
r - размер операнда: 20 разрядов при r=0 и 16 при r=1
Соответственно в ассемблерной мнемонике этот размер указывается буквой
через точку. Например rram.a r5 для 20-разрядного числа и rram.w для 16.
****
С учетом расширенных команд, система команд получилась уже не такой
простой красивой и ортогональной... Но теперь наконец пространство кодов команд
исчерпано почти полностью. Осталось два незадействованных бита в "префиксе",
семь неиспользумых бит в команде RETI, столько же - в бывшей команде NOP.
==============П=Р=И=Л=О=Ж=Е=Н=И=Е========================================
Немножко о том, как устроена память.
С памятью вообще всегда были проблемы. На чем только её не пытались
делать! И на кинескопе от телевизора - заряжая электронным лучем отдельные
точки экрана (или оставляя незаряженными), и на линии задержки в виде трубки,
заполненной ртутью (благо скорость волны в три километра в секунду всё лучше
чем триста тысяч)... Но в конце концов пришли к ферритовым колечкам из
"магнито-твёрдого" материала хорошо сохраняющего остаточную намагниченность.
Материал для них выбирается такой, чтобы кривая намагниченности (т.н. "петля
гистерезиса") была почти прямоугольной. Намагнитить кольцо - элементарно:
пропуская ток в ту или другую сторону через намотанную на него катушку. А вот
определить в какую сторону оно намагничено - посложнее: в кольце магнитный поток
замкнутый - наружу не рассеивается. Но можно сделать так: намотать вторую
катушку, и подав на первую импульс тока попытаться кольцо перемагнитить: если
оно уже было намагничено в эту сторону - эффекта (почти) не будет, а если в
другую - магнитный поток изменится на противоположный и в этот момент во второй
катушке наведётся импульс напряжения.
Далее: берут много колечек, располагают их в виде матрицы (например 64 на 64)
и на каждое наматывать не две, а три катушки - каждая из одного витка: просто
протягивают проволочку через все колечки каждой "строки" матрицы; каждого
"столбца"; и третью - через все - зигзагом по диагонали. С этой третьеё катушки
снимают выходной импульс, а первые две используют для перемагничивания колечек.
Но ток через них пропускают в половину меньше - поэтому перемагничиваются не все
колечки строки или столбца, а только одно, лежащее на их пересечении - то, через
которое пройдут сразу оба этих "полутока". Одна такая матрица - это запоминающее
устройство на один бит. Берут столько матриц, какова длина слова. Всё вместе это
и называется "ферритовый куб".
Колечки брали маленькие - только-только три проволочки просунуть (я видел -
0.8 мм.); перемагничивающие токи - большие (до пол ампера); быстродействие - не
супер (единицы микросекунд). Зато память статическая, а главное -
энергонезависимая. И относительно дешевая - пока микросхемы динамической памяти
не появились.
Это значит было ОЗУ - оперативное запоминающее устройство. ПЗУ тех времён
(постоянное запоминающее устройство с навечно еще на заводе прошитой
информацией) делалось тоже на колечках, но размером куда как по-больше и из
"магнито-мягкого" материала. Колечек брали столько, скольки разрядное слово в
этом ПЗУ; устанавливали их в один ряд; выходная катушка на каждое из них
наматывалась многовитковая, а входных было столько, сколько слов информации надо
было записать. Причем информация в буквальном смысле прошивалась: брали иголку,
вдевали в неё проволочку для очередного слова и протягивали через весь ряд.
Причем если в данном разряде должна была быть единица - проволочку пропускали
через кольцо, а если ноль - мимо. Потом, когда через эту проволочку пропускался
импульс тока - в тех кольцах сквозь которые она была продета в выходной катушке
наводится импульс напряжения. А в тех через которые нет - импульс небыло.
С появлением микросхем постоянных и полу-постоянных запоминающих устройств
больше так не делают. Но термин "прошить" по отношении к ПЗУ - так с тех времён
и остался.
С появлением микросхем сначала "средней" а потом и "большой" степени
интеграции вообще всё стало проще. Потому как вся сложность теперь у них внутри.
То же самое ОЗУ делится на "статическое" и "динамическое".
Статическое память сделана на триггерах. Триггер состоит из двух
логических элементов (инверторов) и требует для своего построения шесть
транзисторов в интегральном исполнении. В неинтегральном, т.е. на отдельных
элементах - только два, но зато еще кучу мелких деталек. Потому-то и стремились
сделать ОЗУ на чем-то подешевше - регистры процессора так делать можно,
"массовую" память - дороговато даже в виде микросхемы. За то быстродействие -
максимально высокое (какое вообще обеспечивает элементная база), и главное -
никакого "обслуживания" не требует (потому и "статическая") - хранит записанную
информацию до тех пор, пока не будет записано что либо другое, или пока не
пропадёт питание. (Т.е. такая память, увы, энергозависимая.)
Работает триггер как детские качели в виде доски на опоре, с которых
согнали ребятишек, а вместо них положили круглую железную болванку: она
перекатится на тот край, который внизу и будет удерживать качели в таком вот
состоянии до тех пор, пока этот край кто ни будь не приподнимет и не перекатит
таким образом болванку на другую сторону. (Но разумеется надо принять меры,
чтобы она совсем с этой доски не скатилась.) А вообще-то там два логических
элемента включены так, что выход каждого из них подключен к одному из входов
другого. Логические элементы взяты "с инверсией", т.е. переворачивающие входной
сигнал, превращающие логический ноль в единицу и наоборот. В результате единица
на выходе одного из них удерживает соседа в состоянии "ноль" (и наоборот) - в
точности как железная болванка качели. И "перекатывается" оно через состояние
равновесия максимально быстро, стоит только один из элементов привести с помощью
дополнительных входов в состояние, противоположное текущему. А вот а момент
начальной подачи питания триггер перевалится на одну из сторон случайным
образом - усиливая малейшую асимметрию. Чем кстати иногда и пользуются, создавая
эту асимметрию искусственно - как в "динамической" памяти.
В этой самой "динамической" памяти запоминающий элемент - конденсатор. Если
он заряжен - "единица", если нет - соответственно "ноль". Но заряд с
конденсатора постепенно утекает, и его приходится время от времени проверять, и
если там была "единица" - заряжать по-новой. Не очень часто - пару тысяч раз в
секунду. Именно по-этому память и называется "динамической", а этот процесс -
"регенерацией". Расположены такие конденсаторы тоже в виде матрицы; к каждому
из них прилагается ключ из одного полевого транзистора, подключающий его к шине,
проходящей через всю строку. Или не подключающий. Затворы всех транзисторов
одного столбца подключены к управляющей шине. А на конце информационной шины
находится такой вот триггер. К другому его выходу (он же вход) подключено нечто,
выглядящее (электрически выглядящее!) почти так-же как строка этой матрицы -
для симметрии. А вся микросхема, так же как и одна матрица ферритового куба,
хранит один бит.
Работает оно так: если мы хотим что-то записать - записываем каким-то
образом нужные нам биты в столбец триггеров, после чего выбираем один из
столбцов запоминающей матрицы - все ключи столбца открываются и находящиеся за
ними конденсаторы заряжаются. Или разряжаются - в зависимости от того, что на
соответствующем триггере - ноль или единичка. Если мы хотим что-то считать, то
сначала выбираем один из столбцов и только после этого подаём питание на
триггера. В результате чего, будучи в начальный момент времени в неопределенном
состоянии, они устанавливаются в ноль или в единицу в зависимости от того, был
ли на подключенном к шине конденсаторе хоть какой-то заряд. Вернее от того,
хватило ли этого заряда придать информационной шине потенциал, хоть чуть чуть
выше опорного, поданного на вторую половину триггера. И сразу-же - как только
триггер окончательно перевернулся, напряжение с него заряжает конденсатор до
полного. Или не заряжает - если ноль. (Т.е. при считывании сразу же
автоматически происходит и регенерация.) Теперь осталось выбрать с помощью
выходного мультиплексора значение с одного из триггеров и выдать его наружу.
Впрочем запись происходит так же в точности: снаружи устанавливается значение
только одного из битов столбца, а все остальные остаются без изменения. (Более
того - регенерируются.) Если требуется только регенерация - элемент из столбца
выбирать не надо. Адрес на такую микросхему (для экономии выводов) обычно
подаётся в два приёма - сначала номер столбца, а потом номер элемента в нём. Ну
так при регенерации подают только первую часть адреса.
Быстродействие такой памяти прямо скажем - не очень; причем она не просто
энергозависимая, а еще и требует постоянной регенерации. На что тоже тратится
некоторое время и заниматься этим должно специальное устройство. (Хотя в
последнее время механизм регенерации встраивают в саму микросхему.) Но всё
окупается предельной простотой и регулярностью - всего один транзистор на бит,
а значит и максимально большой емкостью.
ПЗУ в виде микросхем делятся на "совсем постоянные" - информация в которые
прошивается еще в процессе изготовления (их еще называют "масочные" ПЗУ);
однократно программируемые ("пережоговые"); а так же многократно - с
ультрафиолетовым и с электрическим стиранием. К последним относится т.н.
"флеш-память", отличающаяся от всех прочих в основном тем, что чатать её можно
как угодно, а вот записывать - только довольно большими страницами. Стирать-же
вообще блоками из множества (например из 128) страниц. Слово "флешь" дурацкое -
совершенно ни с чем не ассоциируется ни по звучанию ни по смыслу - в переводе
обозначает "вспышка". Типа чпок - и всё стерлось, что неправда: это чтение
происходит максимально быстро, запись - долго, а стирание - очень долго - до
единиц миллисекунд.
Масочные ПЗУ обычно имеют вид матрицы, в которой на пересечении строки и
столбца стоит диод - если этот бит "единица", или не стоит - если "ноль".
Наличие или отсутствие соответствующего диода определяется рисунком на одной
(последней) или нескольких масках, используемых в технологическом цикле
производства данной микросхемы.
Если кто не помнит: микросхемы изготавливаются методом "фотолитографии.
Сначала из расплава и без того чистого кремния выращивается монокристалл -
этакая колбаса, толщиной сантиметров пятнадцать и длиной метра два с половиною.
Далее материал дополнительно очищается методом зонной плавки: нагреватель и
вместе с ним расплавленная зона медленно движется вдоль кристалла; примеси,
нарушающие структуру растущего кристалла, стремятся остаться в расплаве и
перемещаются вместе с ним. А потом конец кристалла, где они все собрались,
просто отрезают и выбрасывают. Далее кристалл режут на тоненькие пластинки,
шлифуют их, полируют, и вот после этого оно и начинается: на поверхность
наносят специальный светочувствительный материал ("фоторезист"); накладывают эту
самую маску, имеющую вид стеклянной пластинки с непрозрачным рисунком, и сквозь
неё этот фоторезист засвечивают. Далее его "проявляют" - незасвеченные участки
смываются специальным раствором а засвеченные остаются (хотя может быть и
наоборот). После чего пластина помещается в печку, где через окна в слое
фоторезиста в материал пластины проникают примеси и образуют таким макаром
области с "p" или "n" проводимостью (напомню: материал пластины - сверхчистый,
т.е. ничего кроме собственно кремния в ней просто нет) из которых в конечном
итоге и получаются составляющие микросхему диоды и транзисторы. (Нагревание в
печке - чтобы диффузия атомов примесей в глубь материала занимала не годы, а
часы.) Далее фоторезист смывают; наносят новый; накладывают следующую маску и
всё по-новой для другой примеси... Есть в арсенале методов фотолитографии так-же
травление для "прокапывания" канавок, выращивание материала (хоть того же
самого, хоть другого) из газовой фазы; окисление для образования изоляции (оксид
кремния - хороший диэлектрик), осаждение металла - для создания проводников...
На одной пластине одновременно образуется много одинаковых микросхем. Далее её
режут на отдельные микросхемы, тестируют с целью отбраковки негодных (села на
маску самая мелкая пылинка, закрыла собою часть рисунка - всё, хана -
микросхему в брак); годные устанавливают в корпус, подключают его выводы
к контактным площадкам с помощью проволочек из сверхчистого золота, корпус
герметизируют. Всё.
Вот комплект картинок на масках и определяет топологию будущей микросхемы, и
в частности информацию, записанную в имеющейся в одном из её уголков ПЗУ.
Пережоговое ПЗУ устроено так же в точности как и масочное, но в нём при
изготовлении есть все диоды (т.е. записаны все единицы), за то последовательно с
каждым из них - "плавкий предохранитель" в виде тоненького места на проводящей
дорожке. А как известно - где тонко, там и рвётся. При программировании, выбрав
подачей адреса нужный бит, пропускают через изображающий его диод большой ток.
Плавкий предохранитель сгорает - материал проводника в этом месте просто
испаряется. И в результате получается "ноль".
Программируется такая микросхема, разумеется, только один раз.
ПЗУ с электрическим и с ультрафиолетовым стиранием устроены почти
одинаково: там в качестве запоминающего элемента используется не диод, а полевой
транзистор с "плавающим" затвором.
Полевой транзистор потому и "полевой", что управляется электрическим полем,
которое обычно создаётся напряжением, приложенным к его "затвору". (А не как
биполярный, где маленький ток базы управляет гораздо большим током коллектора.)
Область кристалла, по которой собственно и протекает ток (от "истока" к "стоку")
называется "канал". Он располагается у самой поверхности кристалла, а над ним,
поверх очень тоненького слоя диэлектрика - вот этот самый "затвор" - полоска
металлизации, к которой и прикладывается управляющее напряжение. (Это наиболее
распространённый транзистор - "с изолированным затвором", он-же МДП-транзистор,
что означает: металл-диэлектрик-полупроводник. Ну или МОП-транзистор - что тоже
самое, разве что диэлектрик - окись кремния.) Работает он так: по каналу течет
ток (при наличии напряжения между истоком и стоком разумеется) - вот сколько
носителей тока в канале, такой и течет. Носителей (электронов или дырок - какой
канал) может быть много, тогда канал называется "встроенным" и ток потечет
солидный. А может не быть совсем, тогда и тока не будет, а канал называется
"индуцированный". Теперь к затвору прикладываем напряжение. Если оно того же
знака, что и носители - электрическое поле их отталкивает, они разбегаются из
области канала (глубина которой - доли микрона) в глубь кристалла и ток через
канал уменьшается. Возможно вообще до нуля. Если напряжение на затворе
противоположного знака - носители притягиваясь к нему, собираются из глубины
кристалла в область канала, в результате чего ток через канал увеличивается.
Ну так вот: в ПЗУшке в качестве запоминающего элемента используется
транзистор, затвор которого сделан из материала (как правило это нитрид
кремния), в коем электрон вязнет как в болоте. Тоесть если приложить напряжение
по-больше (вольт двадцать) - силы, действующей на электрон хватает чтобы
пропихнуть его вглубь нитридного болота... Где он и застревает. И при обычных
рабочих напряжениях (вольт пять) будет выбираться оттуда лет семьдесят -
восемьдесят. Заряд этих застрявших в затворе электронов и открывает (или
напротив - закрывает) транзистор. Но если осветить его ультрафиолетом (для чего
в крышке микросхемы делают прозрачное окошко) - нитридное болото разжижается и
за пол часика все электроны из затворов стекают - ПЗУ переходит в исходное
"стертое" состояние. То же самое происходит если приложить такое же напряжение
как при программировании, но в другую сторону. Впрочем сейчас элементы микросхем
имеют субмикронные размеры и напряжения более двух вольт для программирования
уже достаточно.