Спецификация Виртуальной Машины Uxn
Документ содержит полное техническое описание архитектуры виртуальной машины Uxn, необходимое для написания собственного эмулятора. Uxn — это 8-битная стековая виртуальная машина с 16-битной адресацией, разработанная Devine Lu Linvega (Hundred Rabbits).
1. Обзор архитектуры
- Тип: Стековая машина (без регистров общего назначения).
- Память: 64 КБ (65536 байт) линейной оперативной памяти.
- Стеки: Два независимых циклических стека по 255 байт — Рабочий стек (Working Stack, WS) и Стек возврата (Return Stack, RS).
- Ввод-вывод: Осуществляется через системную шину (Device Page), поддерживающую до 16 устройств (по 16 портов на каждое).
- Инструкции: 32 базовых опкода, расширяемых 3 битами режимов (флагами), что дает в сумме 256 возможных инструкций.
2. Структура памяти
Вся адресуемая память (RAM) имеет размер ровно 0x10000 (65536) байт.
Карта памяти
| Диапазон адресов | Описание |
|---|---|
0x0000 - 0x00FF | Zero-Page (Нулевая страница). Быстрая память для инструкций LDZ / STZ. Часто используется для хранения глобальных переменных. |
0x0100 | Reset Vector (Вектор сброса). Точка входа в программу. При старте ВМ файл прошивки (ROM) копируется в память начиная с этого адреса, и Program Counter (PC) устанавливается в 0x0100. |
0x0100 - 0xFFFF | Пространство пользователя. Код программы и данные. |
Стеки
Машина имеет два стека — Рабочий (Working Stack, WS) и Стек возврата (Return Stack, RS).
- Каждый стек вмещает 255 байт данных (размер 0xFF).
- Указатель стека (
SP) — это 8-битное беззнаковое целое число. - Важно: Стеки являются циклическими (ring buffers). При переполнении или опустошении указатель стека просто переполняется (оборачивается по модулю 256, фактически от 0 до 255). Ошибки
Stack OverflowилиStack Underflowаппаратно игнорируются, ВМ не "падает". - Значение
SP = 0означает пустой стек. Добавление элемента увеличиваетSPи записывает значение.
Страница устройств (Device Page)
Устройства находятся вне основного адресного пространства в 64 КБ.
- Размер страницы устройств: 256 байт.
- Машина поддерживает до 16 устройств.
- Каждому устройству выделено 16 портов (по 1 байту).
- Доступ к портам осуществляется только инструкциями
DEI(Device In) иDEO(Device Out).
3. Кодирование инструкций (Опкоды)
Каждая инструкция занимает ровно 1 байт (вместе с ее режимами) и может сопровождаться немедленными данными (immediate data), если это инструкция LIT или переходы JCI/JMI/JSI.
Структура байта инструкции:
Бит: 7 6 5 4 3 2 1 0
+---+---+---+---+---+---+---+---+
| K | R | 2 | OPCODE |
+---+---+---+---+---+---+---+---+
Флаги (Режимы)
- Бит 7 — Keep (
k): Режим сохранения. Если флаг установлен, инструкция не удаляет свои аргументы из стека после выполнения (работает как копирование перед выполнением). - Бит 6 — Return (
r): Режим возврата. Если флаг установлен, инструкция берет аргументы из Стека возврата (RS) и кладет результат туда же (по умолчанию операции работают с Рабочим стеком). - Бит 5 — Short (
2): Режим 16-битных значений. Если флаг установлен, инструкция оперирует 16-битными словами (shorts) вместо 8-битных байтов. В стеке 16-битные значения хранятся в формате High-byte, Low-byte (Big-Endian). То есть старший байт добавляется в стек первым, младший — вторым.
Базовые 32 Опкода (Биты 0-4)
Опкоды делятся на логические, арифметические и операции с памятью.
Символом a, b, c обозначены элементы на стеке до вызова. Вершина стека — это самый правый элемент (например, b при a b).
Управление стеком и Логика
| Код | Мнемоника | Описание | Состояние стека (до -- после) |
|---|---|---|---|
0x01 | INC | Инкремент. Увеличивает верхнее значение на 1. | a -- a+1 |
0x02 | POP | Снять со стека. Удаляет верхнее значение. | a -- |
0x03 | NIP | Удалить второе значение. | a b -- b |
0x04 | SWP | Поменять местами два верхних значения. | a b -- b a |
0x05 | ROT | Циклический сдвиг трех верхних значений. | a b c -- b c a |
0x06 | DUP | Дублировать верхнее значение. | a -- a a |
0x07 | OVR | Скопировать второе значение наверх. | a b -- a b a |
0x08 | EQU | Равно. Пушит 01 если a == b, иначе 00. | a b -- c |
0x09 | NEQ | Не равно. | a b -- c |
0x0A | GTH | Больше. Пушит 01 если a > b. | a b -- c |
0x0B | LTH | Меньше. Пушит 01 если a < b. | a b -- c |
0x0C | JMP | Переход. Изменяет PC на значение a. (Если байт — то относительный сдвиг. Если Short — абсолютный адрес). | a -- |
0x0D | JCN | Условный переход. Переход на b, если a != 0. | a b -- |
0x0E | JSR | Вызов подпрограммы. Пушит адрес возврата (PC) на Стек возврата, переходит по адресу a. | a -- |
0x0F | STH | Перенос (Stash). Переносит значение a на противоположный стек. | a -- |
Память и Ввод/Вывод
| Код | Мнемоника | Описание | Стековые аргументы |
|---|---|---|---|
0x10 | LDZ | Чтение из Zero-Page (8-битный адрес). | addr8 -- val |
0x11 | STZ | Запись в Zero-Page. | val addr8 -- |
0x12 | LDR | Относительное чтение (от PC + addr8). | addr8 -- val |
0x13 | STR | Относительная запись (по PC + addr8). | val addr8 -- |
0x14 | LDA | Абсолютное чтение (по 16-битному адресу). | addr16 -- val |
0x15 | STA | Абсолютная запись (по 16-битному адресу). | val addr16 -- |
0x16 | DEI | Чтение с устройства. | port8 -- val |
0x17 | DEO | Запись в устройство. | val port8 -- |
Арифметика и Битовые операции
| Код | Мнемоника | Описание | Стековые аргументы |
|---|---|---|---|
0x18 | ADD | Сложение. | a b -- a+b |
0x19 | SUB | Вычитание. | a b -- a-b |
0x1A | MUL | Умножение. | a b -- a*b |
0x1B | DIV | Деление. (Деление на ноль дает 0). | a b -- a/b |
0x1C | AND | Битовое И. | a b -- a&b |
0x1D | ORA | Битовое ИЛИ. | a b -- a|b |
0x1E | EOR | Битовое исключающее ИЛИ (XOR). | a b -- a^b |
0x1F | SFT | Сдвиг. Младший полубайт b — влево, старший — вправо. | a b -- a >> (b >> 4) << (b & 0x0F) |
Примечание к SFT: Значение сдвигается вправо на старший ниббл b, а затем результат сдвигается влево на младший ниббл b.
4. Особенный Опкод 0 (Immediate инструкции)
Инструкция с базовым кодом 0x00 особенная. Она реализует инструкции чтения аргументов прямо из памяти (то, что идет сразу за PC), в зависимости от активных флагов.
| Байт | Мнемоника | Описание |
|---|---|---|
0x00 | BRK | Break. Останавливает выполнение текущего вектора / программы. |
0x20 | JCI | Условный относительный переход (Short). Снимает 1 байт со стека; если не 0, прибавляет следующий short из памяти к PC. Иначе перескакивает этот short (PC = PC + 2). |
0x40 | JMI | Безусловный относительный переход. Безусловно прибавляет следующий short из памяти к PC. |
0x60 | JSI | Вызов с относительным переходом. Кладет адрес возврата (PC + 2) на Стек возврата, затем делает JMI. |
0x80 | LIT | Literal. Читает 1 байт из памяти за PC и кладет на стек. PC инкрементируется на 1. |
0xA0 | LIT2 | Literal (Short). Читает 16-битный short из памяти за PC и кладет на стек. PC инкрементируется на 2. |
0xC0 | LITr | Literal Return. Читает 1 байт и кладет на Стек возврата. |
0xE0 | LIT2r | Literal Return (Short). Читает short и кладет на Стек возврата. |
5. Детали реализации и нюансы для разработчика
Исполнительный цикл (Execution Loop)
Эмулятор должен выполнять следующий цикл, пока не встретит BRK:
- Прочитать байт памяти по адресу
PC(Это опкод). - Инкрементировать
PC. - Декодировать режимы
Keep,Short,Return. - Выполнить логику опкода.
- Обновить указатели стека в зависимости от количества извлеченных/добавленных элементов. При
Keep-режиме указатель стека после извлечения аргументов возвращается на место, а затем к нему добавляется результат.
Адресация 16-битных значений (Short Mode)
При включенном режиме 2 (Short Mode), инструкция забирает из стека по 2 байта на каждый аргумент (сначала H, затем L) и кладет 16-битные значения.
Например, при операции ADD2 (0x38):
- Читаются
L2иH2для аргументаb. - Читаются
L1иH1для аргументаa. - Производится 16-битное сложение.
- Вычисляются
H_resиL_resи отправляются обратно в стек.
Операции переходов (JMP / JCN)
- Байтовый (8-битный) аргумент: Интерпретируется как знаковое число (от -128 до +127). Это значение прибавляется к текущему
PC(относительный переход). - 16-битный (Short) аргумент: Интерпретируется как абсолютный адрес.
PCустанавливается равным этому значению.
Устройства (Varvara) - Основы
Uxn как процессор ничего не знает о графике или звуке. Он только читает и пишет байты через DEI / DEO.
Если вы хотите, чтобы ВМ что-то выводила (экосистема Varvara), реализуйте минимум системный порт и консоль:
- Устройство 1 (Порты
0x10-0x1F): Console. Запись в порт0x18(Write) должна выводить символ в стандартный вывод (STDOUT). - Устройство 0 (Порты
0x00-0x0F): System. Запись в порты системного вектора задает цвета, расширения и позволяет останавливать систему.
Эта спецификация полностью покрывает ядро Uxn (CPU + Memory). Реализуя логику в точности по этим правилам, ваша ВМ сможет запускать любые бинарные .rom файлы, скомпилированные стандартным ассемблером Uxntal.