Страница произведения
Войти
Зарегистрироваться
Страница произведения

О системах команд (пишется)


Автор:
Жанр:
Детская
Опубликован:
26.07.2015 — 13.11.2017
Читателей:
1
Аннотация:
приложение к главе 7 книги о Фокале: пока только про систему команд PDP-11 и MSP-430 (ред.2.1 от 13.11.16 - добавил: расширенный режим МСП-430)
Предыдущая глава  
↓ Содержание ↓
↑ Свернуть ↑
  Следующая глава
 
 

как работать с константами и с прямыми адресами. То есть чтобы обратиться

например к тому-же консольному терминалу, его адрес (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 потому что переноса за пределы разрядной сетки не будет

Предыдущая глава  
↓ Содержание ↓
↑ Свернуть ↑
  Следующая глава



Иные расы и виды существ 11 списков
Ангелы (Произведений: 91)
Оборотни (Произведений: 181)
Орки, гоблины, гномы, назгулы, тролли (Произведений: 41)
Эльфы, эльфы-полукровки, дроу (Произведений: 230)
Привидения, призраки, полтергейсты, духи (Произведений: 74)
Боги, полубоги, божественные сущности (Произведений: 165)
Вампиры (Произведений: 241)
Демоны (Произведений: 265)
Драконы (Произведений: 164)
Особенная раса, вид (созданные автором) (Произведений: 122)
Редкие расы (но не авторские) (Произведений: 107)
Профессии, занятия, стили жизни 8 списков
Внутренний мир человека. Мысли и жизнь 4 списка
Миры фэнтези и фантастики: каноны, апокрифы, смешение жанров 7 списков
О взаимоотношениях 7 списков
Герои 13 списков
Земля 6 списков
Альтернативная история (Произведений: 213)
Аномальные зоны (Произведений: 73)
Городские истории (Произведений: 306)
Исторические фантазии (Произведений: 98)
Постапокалиптика (Произведений: 104)
Стилизации и этнические мотивы (Произведений: 130)
Попадалово 5 списков
Противостояние 9 списков
О чувствах 3 списка
Следующее поколение 4 списка
Детское фэнтези (Произведений: 39)
Для самых маленьких (Произведений: 34)
О животных (Произведений: 48)
Поучительные сказки, притчи (Произведений: 82)
Закрыть
Закрыть
Закрыть
↑ Вверх