Программирование микропроцессора

Для удобной работы есть расширение для IDE visual studio code ссылка

upd: добавлен Debugger

Препроцессор icX

   Итак, обновление 0.1.1630.7644 принесло нам новый чип, а обновление 0.1.1640.7680 проапгрейдило его до микропроцессора. Он может выполнять достаточно сложные расчёты и способен заменить огромные поля микросхем, одновременно с этим увеличивая скорость и синхронность вычислений.

**Внимание!** Длина программы ограничена 128 строками. В это число входят как команды, так и тэги и пустые строки!

**Внимание!** Длина строки ограничена 52 символами! Но можно и 54 при вставке кода по кнопке "PASTE"

**Внимание!** Команды, тэги, псевдонимы и имена параметров регистрозависимы!

### Регистры

Шестнадцать внутренних регистров микропроцессора **r0**-**r15** предназначены для хранения результатов промежуточных расчётов и/или внутренних переменных (например, счётчика цикла). Регистру можно задать псевдоним, текстовое имя, по которому в дальнейшем можно ссылаться на данный регистр. Ещё два регистра имеют специальное значение и предопределённые псевдонимы. Регистр **r16**, именуемый также **sp** служит указателем на стек, регистр **r17** или **ra** содержит адрес возврата для последнего вызова команды jal или b\*al.

Кроме прямого доступа к регистрам возможна и их косвенная адресация, когда регистр хранит номер регистра, к которому нужно получить доступ. Косвенная адресация записывается добавлением символа **r** перед именем регистра. Глубина косвенной адресации не лимитирована.

```
r0 - содержимое регистра r0 (если представить регистры, как массив r, то это эквивалентно r[0])
rr0 - содержимое регистра, номер которого хранится в r0 (r[r[0]])
rrr0 - содержимое регистра, номер которого хранится в регистре, номер которого хранится в r0 (r[r[r[0]]])

```

и т.д.

При задании псевдонима косвенная адресация разворачивается до текущего значения, при использовании псевдонима косвенная адресация не применяется.

Значения всех регистров имеют тип double (вещественное двойной точности).

### Стек

Под стек выделен отдельный блок памяти микропроцессора размером 512 ячеек. Регистр **r16** или **sp** используется для организации стека и указывает на ячейку, следующую за верхушкой стека. Для работы со стеком предназначены три команды - **push**, **pop** и **peek**.

**push** "кладёт" на верхушку стека значение операнда и увеличивает sp на 1.
**pop** уменьшает sp на 1 и "стягивает" в операнд значение с верхушки стека.
**peek** просто записывает в операнд значение с верхушки стека, не двигая указатель стека.

Микропроцессор не содержит контроля выхода sp за пределы стека, необходимо контролировать стек самостоятельно.

### Порты

Чип имеет шесть портов ввода-вывода: **d0**-**d5**. Через сокет каждый порт может быть настроен на какое-либо оборудование, находящееся в той же информационной сети.

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

```
l r0 d0 On

```

прочитает в регистр r0 значение параметра **On** оборудования, доступного по каналу d0. В некоторых командах может указываться номер слота.

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

Порты могут адресоваться косвенно через регистр. При этом порту db соответствует номер -1.

```
d0 - порт данных с номером 0 (d[0])
dr0 - порт данных с номером, записанным в регистре r0 (d[r[0]])
drr0 - порт данных с номером, записанным в регистре, номер которого записан в регистре r0 (d[r[r[0]]])

```

и т.д.

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

```
ls r1 d0 r0 OccupantHash

```

прочитает в r1 значение параметра **OccupantHash** из слота, номер которого находится в r0, оборудования, подключенного по каналу d0.

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

Значения всех портов имеют тип double (вещественное двойной точности).

### Выполнение программы

Команды программы выполняются последовательно, начиная с первой строки, имеющей номер 0. Специальные команды условных и безусловных переходов служат для ветвления и зацикливания программы.

Переход можно выполнить как на указанную строку, так и на номер строки взятый из регистра, прямо, косвенно или по псевдониму.

Относительный переход выполняется с указанием смещения от текущей строки. Смещение 0 соответствует текущей строке и зацикливает код, +1 - следующая строка, -1 - предыдущая строка.

Специальная группа команд jal и b\*al предназначены для организации подпрограмм. Помимо перехода по указанному адресу они записывают в специальный регистр **r17** или **ra** номер следующей строки. Для возврата из подпрограммы достаточно выполнить переход по этому адресу. Если необходимо использовать несколько вложенных подпрограмм, то для сохранения адреса возврата используйте стек.

За один тик (0.5 секунды) выполняется не более 128 команд программы. Для приостановки программы до следующего тика служит специальный оператор **yield**.

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

### Приближённые сравнения

Специальная группа команд sap, sna, b[r]ap и b[r]na использует приближённое сравнение вещественных чисел по формуле

```
x ≈ y с точностью e ⇒

 x
 −
 y

 <
 m
 a
 x
 (
 e
 ×
 m
 a
 x
 (

 x

 ,

 y

 )
 ,
 f
 l
 o
 a
 t
 .
 e
 p
 s
 i
 l
 o
 n
 ×
 8
 )

```

Таким образом, числа 80 и 80.5 будут приблизительно равны с точностью 0.1:

```
1.401298e-45 ∙ 8 = 1.1210384e-44
max(8.05, 1.1210384e-44) = 8.05
0.5 < 8.05

```

### Тригонометрические функции

Микропроцессор содержит набор тригонометрических функций. Функции **sin**, **cos** и **tg** принимают аргумент в радианах (не градусах!), **asin**, **acos**, '**atan** возвращают результат в радианах.

### Синтаксис

Каждая строка представляет команду с набором операндов, комментарий или тэг. Между собой команда и операнды разделяются одним пробелом. Комментарий начинается с символа **#** в начале строки и продолжается до конца строки.

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

### Условный оператор

Условный оператор, это оператор, который возвращает в результат 1 или 0 в зависимости от того истинно или ложно выбранное условие. Оператор - записывается как составная команда, которая строиться следующим образом (без пробелов) - **s [вид условия {z}]**

где:

**z** - Признак сравнения с нулем (за исключением видов условий **dns** и **dse**). Если он указан, то второй операнд сравнения (Операнд 3 в таблице) не требуется.

Например, команда **sle r0 r1 12** , поместит в регистр **r0** значение 1 если значение регистра **r1** меньше или равно **12**. И **0** - во всех остальных случаях.

### Оператор условного перехода

Оператор условного перехода передает выполнение кода программы на указанный адрес в случае истинности условия. В противном случае, выполнение кода продолжается дальше. Это составная команда, которая строиться следующим образом (без пробелов) - **b {r} [вид условия {z}] {al}**

Назначение видов условия и операндов идентично оператору условия, за исключением того что операнда 1 (результата) нет, а адрес перехода всегда указывается последним операндом. Так же есть дополнительные вариации команды:

**r** - Признак относительного перехода. Адрес, в данном случае, это число строк кода, на которое сдвинется дальнейшее выполнение программы. Если признак не указан, то переход - абсолютный. Недопустим, при использовании **al**.

**al** - абсолютный переход с записью номера следующей строки кода в регистр **r17 (ra).** Это позволяет создавать подпрограммы с возвратом назад по команде **j ra**. Недопустим, при использовании **r**

Например, команда **brgez r0 5** , выполнит относительный переход на 5 строчек кода дальше, если значение в регистре **r0** больше или равно **0**.

### Оператор безусловного перехода

Оператор безусловного перехода передает выполнение кода программы на указанный адрес. Команда имеет следующий вид - **j{r} [адрес]**

**r** - Признак относительного перехода.

Вариант команды **j ra** - выполняет переход на адрес записанный в регистре **r17**

### Специальное число NaN

При выполнении некоторых команд в качестве результата может быть возвращено специальное значение NaN ("not-a-number"). По сути это признак ошибки. Но возврат такого числа не останавливает выполнение программы и такой результат можно и нужно дальше обрабатывать.

В настоящее время с NaN умеют работать следующие команды:

 snanz
 bnan
 brnan

Остальные команды сравнения при попытке обработать регистр, содержащий NaN, вернут ошибку и остановят выполнение программы.

### Пакетное чтение и запись

Команды пакетного чтения и записи предназначены для работы с большим количеством устройств, имеющим общий хеш. Причём в качестве общего можно использовать как предопределённый хеш устройства (работа с одинаковыми устройствами), так и дополнительно хеш имени, задаваемое маркировщиком. В последнем случае разработчики дали возможность одновременно работать не со всеми, а только с частью одинаковых устройств, которым игрок задал одинаковое имя.

Список команд пакетного чтения и записи:

Команды **lb**, **sb** и **lbs** работают только с предопределённым хешем, полученным устройством при постройке. А **lbn** и **sbn** уже работают и с предопределённым хешем устройства, и с хешем имени, которое задаётся маркировщиком.

Для преобразования строки с названием (предопределено и есть во встроенной "стационпедии") или именем устройства есть субкоманда **HASH**. Её можно использовать везде, где есть агрумент "хеш", вместо прямого указания хеша.

Примеры использования команд пакетного чтения/записи

```
lb r1 -1388288459 Ratio Average # чтение в регистр r1 среднего уровня заряда со всех больших батарей
lb r1 HASH("StructureBatteryLarge") Ratio Average # тоже самое, но уже с использованием HASH

```

```
sb -2045627372 Vertical r2  # запись в солнечные панели с совмещённым выходом в параметр Vertical значения из r2
sb HASH("StructureSolarPanel") Vertical r2 # тоже самое, но через HASH

```

```
lbn r1 1108423476 HASH("MyDevice") On Sum # считать в r1 сумму значений параметров On со всех ламп с именем MyDevice
sbn HASH("StructureCompositeDoor") HASH("Door_Sector2") Open r2 # записать во все композитные двери с именем Door_Sector2 в параметр Open значение из r2

```

Если при пакетном чтении (команды **lb**, **lbs**, **lbn**) не будет найдено ни одно устройство с подходящим хешем - в результат запишется NaN. Как с ним дальше работать - описано в предыдущем разделе "Специальное число **NaN**".

### Полный список команд

Ниже приведён список команд, содержащий следующие условные обозначения:

**R** - один из регистров r0-r15, в прямой или косвенной адресации

**N** - псевдоним, понятное имя, которое можно задать регистру или каналу данных

**D** - один из входных портов db, d0-d5 в прямой или косвенной адресации

**S** - номер слота порта db, d0-d5

**P** - логический параметр порта db, d0-d5

**C** - константа типа double, заданная непосредственно или по имени

**A** - номер строки, неотрицательное целое число (нумерация начинается с 0)

**T** - тэг (константа со своим номером строки)

**O** - смещение, целое число, указывающее смещение в строках кода относительно текущей

**RM** - режим чтения реагентов, одно из **Contents**, **Required** или **Recipe** (можно 0, 1 или 2, соответственно)

**RC** - код реагента

**HP** - предопределённый хеш устройств, с которыми выполняется операция пакетного чтения **lb**, **lbs**, **lbn** или записи **sb, sbn**

**HN** - хеш имени устройств, заданный маркировщиком, с которым выполняются операции **lbn** или **sbn**

**BM** - режим пакетного чтения, одно из **Average**, **Sum**, **Minimum** или **Maximum** (можно 0, 1, 2 или 3, соответственно)

Логические операторы **and**, **or**, **xor** и **nor** считают "истиной" (true) - любые значения, большие нуля, "ложью" (false) - значения, равные или меньшие нуля. Результат логических операций - истина (true) = 1, ложь (false) = 0.

\*Тригонометрические функции принимают аргумент (а обратные выдают результат) **не в градусах, а в радианах**

### Пример программы

Как принято для микроконтроллеров, поморгаем лампочкой. Собираем сеть из компьютера с платой программатора, сокета с установленным микропроцессором и LED-лампы. Запитываем всё от доступного источника электричества.

Настраиваем канал D0 сокета на лампу. Включаем сокет и компьютер. Нажимаем в правом нижнем углу экрана программирования кнопку **Edit**. Пишем программу:

````
# aliases section       # Строка  0: Комментарий, блок меток
alias LED d0            # Строка  1: Метка LED будет показываться на винте D0
alias counter r0        # Строка  2: Регистру r0 задан псевдоним counter
# main loop             # Строка  3: Комментарий, основной цикл
main:                   # Строка  4: Тэг, на который можно сделать переход
s LED On 1              # Строка  5: Записываем 1 в параметр On лампы (включаем)
yield                   # Строка  6: Пропускаем тик
s LED On 0              # Строка  7: Записываем 0 в параметр On лампы (выключаем)
move counter 4          # Строка  8: Записываем 4 в регистр r0
loop:                   # Строка  9: Метка цикла ожидания
yield                   # Строка 10: Пропускаем тик
sub counter counter 1   # Строка 11: Уменьшаем значение r0 на 1
bgtz counter loop       # Строка 12: Если значение r0 больше нуля, то переходим на начало цикла ожидания
j main                  # Строка 13: Иначе зацикливаем код, переходя на начало основного цикла

````

Записываем программу кнопкой **CONFIRM** и передаём в микроконтроллер кнопкой **Export**. Теперь лампочка через каждые четыре тика загорается на один тик.

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

## Интересные приёмы

*См. отдельную статью:* Продвинутые приёмы программирования микропроцессора

У многих может возникнуть логичный вопрос: "Как массово управлять устройствами (например, солнечными панелями), если их количество больше 5?". Для этого обратите внимание на команды lb - пакетное чтение и sb - пакетная запись.

```
alias StarSensor d0

begin:
l r0 StarSensor Vertical
sb -539224550 Vertical r0

yield
j begin
```
Пример будет корректно работать на Луне при условии, что сенсор размещен на стене контактами вниз и "смотрит" на восток.

## Экспорт, импорт и перенос кода.

В редакторе кода (на компьютере с платой программирования) сверху есть кнопка "библиотека". При нажатии на неё появляется окно со списком локальных сохраненных программ и программ из подписок Steam. В этом окне можно создать новый файл программы, опубликовать существующий файл в Steam (если игра куплена), записать текущую программу в файл и вставить код из файла. Таким образом. Вы можете хранить свои программы или пользоваться чужими.

Физически локально сохраненные программы хранятся в папке **C:\Users\<имя пользователя>\Documents\My Games\Stationeers\scripts** или попросту **Мои документы\My Games\Stationeers\scripts.** Для каждой программы создается своя папка. Сама программа хранится в файле instruction.xml в формате XML (очевидно) в кодировке UTF-8. Этот файл можно редактировать любым редактором (главное правильно сохранять в UTF), а так же переносить на другие компьютеры (вместе с папкой).