Задний план
Мне нравится мой старый 8-битный чип 6502. Здесь даже забавно решить некоторые проблемы на PPCG в машинном коде 6502. Но некоторые вещи, которые должны быть простыми (например, чтение данных или вывод в stdout), излишне громоздки в машинном коде. Так что у меня в голове есть грубая идея: изобрести собственную 8-битную виртуальную машину, которая вдохновлена 6502, но с измененным дизайном, чтобы он был более пригодным для решения задач. Начав что-то реализовывать, я понял, что это может быть хорошей задачей, если дизайн виртуальной машины сведен к минимуму :)
задача
Реализуйте 8-битную виртуальную машину, соответствующую следующей спецификации. Это код-гольф , поэтому выигрывает реализация с наименьшим количеством байтов.
вход
Ваша реализация должна принимать следующие входные данные:
Один беззнаковый байт
pc
, это начальный счетчик программы (адрес в памяти, где виртуальная машина начинает выполнение, на0
основе)Список байтов с максимальной длиной
256
записей, это оперативная память для виртуальной машины (с ее начальным содержимым)
Вы можете принять этот вклад в любом разумном формате.
Выход
Список байтов, которые являются окончательным содержимым оперативной памяти после завершения работы виртуальной машины (см. Ниже). Вы можете предположить, что вы получаете только вход, который в конечном итоге приводит к завершению. Разрешен любой разумный формат.
Виртуальный процессор
Виртуальный процессор имеет
- 8-битный программный счетчик,
- 8-битный регистр аккумулятора называется
A
и - вызывается 8-битный индексный регистр
X
.
Есть три флага состояния:
Z
- нулевой флаг устанавливается после того, как какая-то операция приводит к0
N
- отрицательный флаг устанавливается после того, как некоторые операции приводят к отрицательному числу (установлен бит 7 результата)C
- флаг переноса устанавливается добавками и сдвигами для «пропущенного» бита результата
При запуске все флаги сбрасываются, счетчик программ устанавливается на заданное значение, а содержимое A
и X
является неопределенным.
8-битные значения представляют либо
- беззнаковое целое число в диапазоне
[0..255]
- подписал целое число, 2 в дополнение, в диапазоне
[-128..127]
в зависимости от контекста. Если операция переполняет или теряет значение, значение оборачивается (и в случае добавления затрагивается флаг переноса).
прекращение
Виртуальная машина завершает работу, когда
HLT
Инструкция достигается- Доступ к несуществующему адресу памяти
- Счетчик программы работает за пределами памяти (обратите внимание, что он не оборачивается, даже если виртуальной машине предоставлено все 256 байтов памяти)
Режимы адресации
- неявный - инструкция не имеет аргумента, операнд подразумевается
- немедленный - операнд является байтом непосредственно после инструкции
- относительный - (только для ветвления) байт после подписания инструкции (дополнение 2) и определяет смещение, добавляемое к счетчику программы, если берется ветвление.
0
расположение следующей инструкции - абсолютный - байт после инструкции является адресом операнда
- индексируется - байт после инструкции плюс
X
(регистр) является адресом операнда
инструкции
Каждая инструкция состоит из кода операции (один байт) и, в режимах адресации, непосредственного , относительного , абсолютного и индексируемого второго байта аргумента. Когда виртуальный ЦП выполняет инструкцию, он соответственно увеличивает счетчик программы (на 1
или 2
).
Все коды операций, показанные здесь, в шестнадцатеричном формате.
LDA
- загрузить операнд вA
- Коды операций: немедленный:,
00
абсолютный:,02
проиндексированный:04
- Флаги:
Z
,N
- Коды операций: немедленный:,
STA
- сохранитьA
в операнде- Коды операций: немедленный:,
08
абсолютный:,0a
проиндексированный:0c
- Коды операций: немедленный:,
LDX
- загрузить операнд вX
- Коды операций: немедленный:,
10
абсолютный:,12
проиндексированный:14
- Флаги:
Z
,N
- Коды операций: немедленный:,
STX
- сохранитьX
в операнде- Коды операций: немедленный:,
18
абсолютный:,1a
проиндексированный:1c
- Коды операций: немедленный:,
AND
- побитовое и изA
и операнда вA
- Коды операций: немедленный:,
30
абсолютный:,32
проиндексированный:34
- Флаги:
Z
,N
- Коды операций: немедленный:,
ORA
- поразрядно или изA
и операнд вA
- Коды операций: немедленный:,
38
абсолютный:,3a
проиндексированный:3c
- Флаги:
Z
,N
- Коды операций: немедленный:,
EOR
- побитовый xor (исключая или)A
и операнд вA
- Коды операций: немедленный:,
40
абсолютный:,42
проиндексированный:44
- Флаги:
Z
,N
- Коды операций: немедленный:,
LSR
- логическое смещение вправо, сдвиг всех битов операнда на одно место вправо, бит 0 переносится- Коды операций: немедленный:,
48
абсолютный:,4a
проиндексированный:4c
- Флаги:
Z
,N
,C
- Коды операций: немедленный:,
ASL
- сдвиг арифметики влево, сдвиг всех битов операнда на одну позицию влево, бит 7 идет на перенос- Коды операций: немедленный:,
50
абсолютный:,52
проиндексированный:54
- Флаги:
Z
,N
,C
- Коды операций: немедленный:,
ROR
- повернуть вправо, сдвинуть все биты операнда на одно место вправо, перенос идет до бита 7, бит 0 - переносить- Коды операций: немедленный:,
58
абсолютный:,5a
проиндексированный:5c
- Флаги:
Z
,N
,C
- Коды операций: немедленный:,
ROL
- поверните влево, сдвиньте все биты операнда на одну позицию влево, перенос идет в бит 0, бит 7 идет в перенос- Коды операций: немедленный:,
60
абсолютный:,62
проиндексированный:64
- Флаги:
Z
,N
,C
- Коды операций: немедленный:,
ADC
- добавить с переносом, добавлен операнд плюс переносA
, перенос установлен на переполнение- Коды операций: немедленный:,
68
абсолютный:,6a
проиндексированный:6c
- Флаги:
Z
,N
,C
- Коды операций: немедленный:,
INC
- увеличить операнд на единицу- Коды операций: немедленный:,
78
абсолютный:,7a
проиндексированный:7c
- Флаги:
Z
,N
- Коды операций: немедленный:,
DEC
- уменьшить операнд на единицу- Коды операций: немедленный:,
80
абсолютный:,82
проиндексированный:84
- Флаги:
Z
,N
- Коды операций: немедленный:,
CMP
- сравнитеA
с операндом, вычитая операнд изA
, забудьте результат. Перенос очищается при потере, устанавливается иначе- Коды операций: немедленный:,
88
абсолютный:,8a
проиндексированный:8c
- Флаги:
Z
,N
,C
- Коды операций: немедленный:,
CPX
- сравнитьX
- так же, какCMP
дляX
- Коды операций: немедленный:,
90
абсолютный:,92
проиндексированный:94
- Флаги:
Z
,N
,C
- Коды операций: немедленный:,
HLT
- прекратить- Коды операций: неявный:
c0
- Коды операций: неявный:
INX
- увеличениеX
на единицу- Коды операций: неявный:
c8
- Флаги:
Z
,N
- Коды операций: неявный:
DEX
- уменьшениеX
на единицу- Коды операций: неявный:
c9
- Флаги:
Z
,N
- Коды операций: неявный:
SEC
- установить флаг переноса- Коды операций: неявный:
d0
- Флаги:
C
- Коды операций: неявный:
CLC
- снять флажок- Коды операций: неявный:
d1
- Флаги:
C
- Коды операций: неявный:
BRA
- ветка всегда- Коды операций: относительный:
f2
- Коды операций: относительный:
BNE
- ветвь, еслиZ
флаг снят- Коды операций: относительный:
f4
- Коды операций: относительный:
BEQ
- ветвь, еслиZ
установлен флаг- Коды операций: относительный:
f6
- Коды операций: относительный:
BPL
- ветвь, еслиN
флаг снят- Коды операций: относительный:
f8
- Коды операций: относительный:
BMI
- ветвь, еслиN
установлен флаг- Коды операций: относительный:
fa
- Коды операций: относительный:
BCC
- ветвь, еслиC
флаг снят- Коды операций: относительный:
fc
- Коды операций: относительный:
BCS
- ветвь, еслиC
установлен флаг- Коды операций: относительный:
fe
- Коды операций: относительный:
Opcodes
Поведение виртуальной машины не определено, если найден какой-либо код операции, который не соответствует действительной инструкции из приведенного выше списка.
Согласно запросу Джонатана Аллана , вы можете выбрать свой собственный набор кодов операций вместо кодов операций, показанных в разделе « Инструкции ». Если вы это сделаете, вы должны добавить полное сопоставление к кодам операций, использованным выше в вашем ответе.
Сопоставление должно быть шестнадцатеричным файлом с парами <official opcode> <your opcode>
, например, если вы заменили два кода операции:
f4 f5
10 11
Новые строки здесь не имеют значения.
Тестовые случаи (официальные коды операций)
// some increments and decrements
pc: 0
ram: 10 10 7a 01 c9 f4 fb
output: 10 20 7a 01 c9 f4 fb
// a 16bit addition
pc: 4
ram: e0 08 2a 02 02 00 6a 02 0a 00 02 01 6a 03 0a 01
output: 0a 0b 2a 02 02 00 6a 02 0a 00 02 01 6a 03 0a 01
// a 16bit multiplication
pc: 4
ram: 5e 01 28 00 10 10 4a 01 5a 00 fc 0d 02 02 d1 6a 21 0a 21 02 03 6a 22 0a 22 52
02 62 03 c9 f8 e6 c0 00 00
output: 00 00 00 00 10 10 4a 01 5a 00 fc 0d 02 02 d1 6a 21 0a 21 02 03 6a 22 0a 22 52
02 62 03 c9 f8 e6 c0 b0 36
Я мог бы добавить больше тестовых случаев позже.
Справка и тестирование
Чтобы помочь с собственными экспериментами, вот некоторая (полностью не игровая) эталонная реализация - она может выводить информацию трассировки (включая разобранные инструкции) stderr
и конвертировать коды операций во время работы.
Рекомендуемый способ получения источника:
git clone https://github.com/zirias/gvm --branch challenge --single-branch --recurse-submodules
Или оформите ветку challenge
и сделайте git submodule update --init --recursive
после клонирования, чтобы получить мою собственную систему сборки.
Создайте инструмент с помощью GNU make (просто введите make
или, gmake
если в вашей системе, по умолчанию make не GNU make).
Использование :gvm [-s startpc] [-h] [-t] [-c convfile] [-d] [-x] <initial_ram
-s startpc
- начальный счетчик программы, по умолчанию0
-h
- ввод в шестнадцатеричном (иначе двоичный)-t
- отслеживать выполнение доstderr
-c convfile
- конвертировать коды операций в соответствии с отображением, приведенным вconvfile
-d
- сбросить результирующую память в виде двоичных данных-x
- дамп результирующей памяти в виде шестнадцатеричногоinitial_ram
- начальное содержимое ОЗУ, либо шестнадцатеричное, либо двоичное
Обратите внимание, что функция преобразования не будет работать в программах, которые изменяют коды операций во время работы.
Отказ от ответственности: приведенные выше правила и спецификации являются авторскими для вызова, а не для этого инструмента. Это особенно относится к функции преобразования кода операции. Если вы считаете, что инструмент, представленный здесь, имеет ошибку в спецификации, сообщите об этом в комментарии :)
источник
BRA
("ветвь всегда") не вводит ветвь в потоке управления, не должна ли она быть вызванаJMP
?BRA
существует в более поздних конструкциях чипов (у 6502 такой инструкции нет), таких как 65C02 и MC 68000.JMP
Существует также. Разница в том, чтоBRA
используется относительная адресация иJMP
абсолютная адресация. Итак, я просто следовал этим проектам - действительно, это не звучит так логично;)Ответы:
C (gcc) ,
13811338,1255,1073 байтаОгромное улучшение благодаря потолку и Rogem.
Попробуйте онлайн!
Много определений перенесено во флаги компилятора.
Пояснение (ОЧЕНЬ без присмотра):
источник
00
байтов может немного нарушать правила ... Я признаю, что не пытался анализировать этот код ... не могли бы вы сохранить байты, делая ввод / вывод в двоичном формате вместо шестнадцатеричного? Будет разрешено по правилам :)APL (Dyalog Classic) ,
397332330 байтовспасибо @ Adám за -8 байт
Попробуйте онлайн!
источник
f←
?C (gcc) ,
487,480,463,452,447, 438 байтовИспользует это отображение инструкций . Обновление инструкций сбило 9 байтов и, возможно, больше в будущем. Возвращает путем изменения памяти, на которую указывает первый аргумент (
M
). Спасибо @ceilingcat за то, что сбрил несколько байтов.Должен быть скомпилирован с флагами
-DO=*o -DD=*d -DI(e,a,b)=if(e){a;}else{b;} -Du="unsigned char"
(уже включены в байты).Попробуйте онлайн!
препроцессор
Эти два предоставляют более короткий способ разыменования указателей.
Уменьшите количество байтов, необходимых для if-elses и объявлений типов.
Код
Ниже приведена удобочитаемая версия кода с расширенными директивами препроцессора и переименованными переменными для удобства чтения.
инструкции
Инструкции структурированы следующим образом:
Биты 6-7 указывают на арность инструкции (
00
Nullary, Unary01
,10
Binary,11
Binary)Биты 0-2 определяют операнд (ы):
R=0
выбираетA
иR=1
выбираетX
.OP=00
использует регистр в качестве операнда,OP=01
выбирает непосредственный операнд,OP=10
выбирает абсолютный операнд иOP=11
выбирает индексированный операнд.X
), даже если они обычно не могут использоваться согласно спецификации. НапримерINC A
,ADC X, 10
иASL X
все работает.Биты 3-5 определяют условие ветвления: наличие одного из битов указывает, какой флаг проверять (бит 3-> C, бит 4-> N, бит 5-> Z). Если установлен только один бит, инструкция проверяет наличие установленного флага. Чтобы проверить наличие неустановленного флага, возьмите дополнение битов. Например,
110
тесты для неснятого переноса и001
для установленного переноса.111
и000
ветка всегда.Вы также можете переходить к смещению адреса, хранящегося в регистре, что позволяет вам писать функции, или вы можете использовать стандартные режимы индексации.
OP=01
ведет себя как ветвь спецификации.источник
JavaScript (ES6), 361 байт
Принимает как
(memory)(program_counter)
, гдеmemory
находитсяUint8Array
.Выходы путем изменения этого массива.NB: код сжат RegPack и содержит много непечатаемых символов, которые экранируются в приведенном выше представлении источника.
Попробуйте онлайн!
Отображение кода операции и контрольные примеры
Виртуальная машина использует это отображение кода операции .
Ниже приведены переведенные тесты и ожидаемые результаты.
Тестовый пример № 1
Ожидаемый результат:
Тестовый пример № 2
Ожидаемый результат:
Тестовый пример № 3
Ожидаемый результат:
Распакован и отформатирован
Поскольку код сжимается с помощью алгоритма, который заменяет часто повторяющиеся строки одним символом, более эффективно использовать одни и те же блоки кода снова и снова, чем определять и вызывать вспомогательные функции или сохранять промежуточные результаты (например,
M[A]
) в дополнительных переменных.источник
o = ...
строка выполняется для каждой инструкции, это может иметь «непреднамеренные коды операций»?c = M[A] >> 7 & 1
<-&1
действительно нужно здесь?Uint8Array
действительно просто заключает в себе такой список байтов. Поэтому, если размещение байтов в шестнадцатеричной строке является приемлемым способом представления входных данных, почему запрещается помещать их в контейнерный объект ...PHP,
581 585 555532 байта (не конкурирует)принимает коды ПК и OP в качестве целых 10-ти целых чисел из аргументов командной строки,
печатает память в виде списка
[base 10 address] => base 10 value
.Это еще не проверено полностью ; но есть поломка .
Вот карта кодов и обзор моего отображения:
примечание:
код
24
приводит кBNV
(ветвь никогда не = 2 байтаNOP
);04
,08
,0C
Являются псевдонимами дляINX
,CLC
иSEC
и все , что выше
3F
является либо два байтаNOP
или псевдоним для инструкции одномодовых.источник