Хочу рассказать о процессоре, который я разработал в 2016 году. Он реализован на C как виртуальная машина. Мой друг Бьёрн написал для него ассемблер на F#.
Periwinkle представляет собой процессор OISC (one instruction set computer), в отличие от RISC и CISC. У него нет никакой конвейеризации. По сути, производительность не является главной задачей проекта, он создан скорее для удовольствия и в учебных целях.
Моя подруга Алёна придумала ему название Periwinkle, то есть барвинок (этот удивительно живучий цветок считается символом жизненной силы — прим. пер.)
Есть множество типов инструкций для OISC. Но в Periwinkle это инструкция move. Просто перемещаете литерал в регистр или значение из одного регистра в другой. Логические и арифметические операции, ветвление и т.д. выполняются с помощью регистров.
Длина инструкции Periwinkle стабильно 40 бит. Шина данных 32 бита.
У Periwinkle в общей сложности 64 регистра.
Вот описание некоторых:
Программный счётчик (PC)
- Счётчик до 32 бит
- Размер памяти программы составляет 255 40-битных слов (зависит от реализации)
- Перемещение -1 в регистр PC приводит к явной остановке счётчика
Стек общего назначения (STK)
- Стек для чего угодно (16 уровней в глубину)
- Нет сигнала переполнения и неполного стека
- Чтение пустого стека возвращает 0
- Перемещение сюда значения — это операция push
- Перемещение отсюда — операция pop
Генератор случайных чисел (RNG)
- При вызове генерирует случайное 32-битное число
- Перемещение сюда значения кажется бессмысленным
Skip If Zero (SIZ)
- Пропускает следующую инструкцию, если в него переместить нуль
Skip If Non-zero (SINZ)
- Пропускает следующую инструкцию, если в него переместить ненулевое значение
Reference (REF)
- Используется для указания на адрес на основе перемещённого значения
- Большие значения усекаются до 6-битных чисел
Dereference (DEF)
- Разыменование после REF
Зарезервированные регистры (RSV)
- Перемещение сюда значения не имеет эффекта. Регистр по-прежнему будет содержать ноль.
- Можно применить для какой-нибудь задачи при переносе виртуальной машины на микроконтроллер или чего-то еще
- Для будущих/дополнительных регистров
- Можно использовать для удаления элемента из стека общего назначения и операционных регистров, переместив элемент сюда (не рекомендуется)
- При чтении возвращает 0
Регистры общего назначения (GPR0-GPR31)
- Могут содержать 32-битные числа
Нулевой регистр
- Можно использовать для удаления элемента из стека общего назначения и операционных регистров, переместив этот элемент сюда
- Возвращает 0 при чтении
Регистр состояния:
- 0000 0000 0000 0000 0000 0000 000P ZVNC
- Содержит пять флагов (C, N, V, Z, P)
- Carry
- Negative
- Overflow
- Zero
- Регистр PLUS влияет на флаги C, N, V, Z, P
- Регистры AND, OR, XOR влияют на флаги N, Z, P
- Последняя запущенная операция повлияет на регистр состояния
- Перемещение значения здесь кажется бессмысленным.
Но как работают регистры PLUS, AND, OR, XOR? В этих четырёх регистрах есть своеобразный стек, который является на самом деле вычислительным стеком. Когда в стеке вычислений два числа, операция запускается.
Вот пример для регистра PLUS. Схема работает аналогично для остальных трёх регистров.
Но как производить арифметические действия всего с четырьмя операторами? Как я уже говорил, цель этого проекта — развлечение и образование. Поэтому можете самостоятельно синтезировать недостающие действия.
Вычитание производится путём сложения с дополнительным кодом, который представляет отрицательные числа. Дополнительный код образуется инверсией битов, то есть запушив на число 0xFFFFFFFF (2³²-1) через XOR. И прибавив единицу через регистр PLUS. Впрочем, ассемблер поддерживает отрицательные числа, так что не обязательно всё это делать.
Умножение — просто многократное сложение.
Деление — это сколько раз одно число помещается в число. Это можно посчитать через счётчик последовательных вычитаний.
Битовый сдвиг делается умножением на 2 или делением на 2.
Сами подумайте, как синтезировать другие операции.
Если интересно, вот репозиторий github некоторых программ на ассемблере, которые я написал для Periwinkle. Инструкция move работает слева направо:
#50 gpr0 //переместить литерал 50(base-10) в gpr0
gpr0 gpr1 //переместить значение gpr0 в gpr1
Кроме того, попытаюсь выложить исполняемый файл виртуальной машины Periwinkle VM. Для какой платформы делать виртуальную машину? (Windows (x86? x86-64?), Linux (x86? x86-64? ARM?, ARM64? и т.д.?) и т.д.?) Поскольку ассемблер написан на F#, наверное, он может работать везде, нужен только .NET framework, можете заглянуть в Mono.
Если вы знаете архитектуру PIC16, то могли заметить у Periwinkle некоторое сходство с ней (STATUS, SIZ, SINZ, REF, DEF). Действительно, она вдохновила меня на работу как первая архитектура, с которой я начал программировать на ассемблере.
Автор: m1rko