В этом посте:

Программирование на машинном коде

У каждого процессора есть набор команд, которые он может выполнять, и компактное представление этих команд в памяти. Код, написанный в этом представлении, называется машинным кодом.

В процессоре Intel 8080 (аналоге процессора в СМ-1800) есть команды, которые занимают один, два или три байта. Если команда занимает один байт, то процессор считает этот один байт, выполнит команду, и перейдет к следующему байту. Если команда занимает два байта, то процессор считает эти два байта, выполнит команду, и продолжит выполнение с третьего байта, а если команда занимает три байта – с четвертого.

Команды, которые процессор может выполнять, являются очень простыми: например, “считать значение из памяти и записать его в регистр”, “добавить к регистру это значение”, “записать эти значения на стек” и т.д. Для того, чтобы выполнять более сложные операции (например, умножение), нужно написать более сложный код, использующий эти простые команды.

Важное замечание по поводу адресов в коде

Когда мы будем писать код, нам будет нужно указывать в коде адреса в памяти. Например, команды вроде “продолжить выполнение с этого адреса” или “вызвать подпрограмму по адресу”.

Адреса состоят из двух байтов – старшего и младшего. Например, у адреса 0x1234 старший байт будет равен 0x12 и младший байт – 0x34.

Когда инструкция машинного кода принимает адрес, сначала идёт младший байт, а затем – старший байт.

Например, инструкция CALL имеет опкод 0xCD. Поэтому, чтобы выполнить операцию CALL 0x1234, машинный код будет выглядеть так:

CD 34 12

Регистры процессора

Внутри процессора есть несколько ячеек памяти, которые хранят значения, над которыми процессор работает в этот момент. Эти ячейки называются регистрами.

В этом процессоре есть регистры общего и особого назначения. Регистры общего назначения могут быть использованы для хранения любых значений, а особые регистры используются процессором в процессе работы, и изменяются особым образом.

Регистры особого назначения – это:

  • PC (Program Counter) — регистр, который хранит адрес очередной команды, которую процессор будет выполнять. Обычно этот регистр изменяется процессором автоматически, но его можно изменить, вызвав одну из команд JMP и CALL-категорий, тем самым создавая условные переходы и циклы.
  • SP (Stack Pointer) — регистр, который хранит адрес верха стека. Стек – это область памяти, в которую можно добавить значение с помощью команды PUSH, и достать значение с помощью команды POP.
  • F (Flags) — регистр, который на самом деле не существует. На самом деле внутри процессора есть несколько отдельных однобитовых ячеек памяти, которые хранят флаги. Для некоторых задач (например, для сохранения на стек) процессор воспринимает все эти флаги как один общий регистр F, внутри которого три бита имеют определенное значение всегда, а остальные пять бит относятся к значениям флагов.

Регистры общего назначения – это A, B, C, D, E, H и L. Среди них регистр A имеет особенное значение – это регистр, над которым работают все арифметические операции.

Отдельные 8-битные регистры также объединяются в несколько 16-битных регистров – BC, DE и HL. Эти регистры используются для хранения адресов в памяти. В этих парах B, D и H – это верхний байт, а C, E и L – это нижний байт. Среди этих регистров особенное значение имеет регистр HL – это регистр, который хранит адрес в памяти, который надо прочитать или записать (при использовании команд MOV с параметром M).

Опкоды

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

Список опкодов весьма длинный, поэтому он вынесен на отдельную страницу на этом сайте.

Системные вызовы

Когда мы работаем с СМ-1800, в памяти загружена программа-монитор. Когда работает наша программа, мы можем использовать процедуры, которые описаны в программе-мониторе, чтобы выполнять действия вроде ввода-вывода на экран. Это можно сделать с помощью команды CALL, которая имеет опкод 0xCD.

Например, есть процедура по адресу 0x0055, которая принимает на ввод один символ и сохраняет его код в регистре A. Также есть процедура, которая выводит на экран текущее значение регистра A, по адресу 0x0061, и ещё есть процедура по адресу 0x0049, которая переводит курсор на новую строку. Наконец, есть опкод 0xC3, который осуществляет безусловный переход в указанный адрес.

Вот программа, которая считывает один символ, выводит его код, и делает это бесконечно:

0x4000:
CD 55 00    ;CALL 0x0055
CD 61 00    ;CALL 0x0061
CD 49 00    ;CALL 0x0049
C3 00 40    ;JMP 0x4000

Здесь и далее я буду использовать знак точка с запятой (;), чтобы отделять комментарии внутри строчек – именно этот символ часто используется как разделитель комментариев в ассемблере. Слева написаны байты машинного кода, а справа – интерпретация этих байтов как кода ассемблера.

Если записать эту программу в память по адресу 0x4000 и запустить ее, то она будет принимать на вход буквы и выводить их коды на экран.

Коды символов

Это – лишь небольшой пример того, какие системные вызовы доступны в СМ-1800. [Список можно прочитать на этой странице][/system-calls/].

Другие примеры программ

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

Каждую из этих программ нужно расположить в памяти по адресу 0x4000. В программе-мониторе это можно делать командой S.

Вывод строки

Эта программа выводит одну заданную строку (в данном случае – “HELLO, WORLD!”) на экран.

C3 10 40    ; JMP 0x4010
48 45 4C 4C 4F 2C 20 57 4F 52 4C 44 21 00  ; "HELLO, WORLD!\0"
01 03 40    ; LXI B, 0x4003
CD 4F 00    ; CALL 0x004F
C3 40 00    ; JMP 0x0040

Программа Hello World

Сложение двух цифр

Эта программа принимает на вход две цифры и выводит их сумму на экран в шестнадцатиричном формате.

CD 55 00    ; CALL 0x0055
D6 30       ; SUI 0x30
47          ; MOV B,A
CD 55 00    ; CALL 0x0055
D6 30       ; SUI 0x30
80          ; ADD B
CD 61 00    ; CALL 0x0061
CD 49 00    ; CALL 0x0049
C3 00 40    ; JMP 0x4000

Сумма цифр

Здесь мой ввод – это первые две цифры на каждой строке, а последние два символа – это вывод компьютера.

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