Задний план
Мне нравится мой старый 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
абсолютная адресация. Итак, я просто следовал этим проектам - действительно, это не звучит так логично;)