[Перевод] Periwinkle: процессор с одной инструкцией

Хочу рассказать о процессоре, который я разработал в 2016 году. Он реализован на C как виртуальная машина. Мой друг Бьёрн написал для него ассемблер на F#.

Periwinkle представляет собой процессор OISC (one instruction set computer), в отличие от RISC и CISC. У него нет никакой конвейеризации. По сути, производительность не является главной задачей проекта, он создан скорее для удовольствия и в учебных целях.

Моя подруга Алёна придумала ему название Periwinkle, то есть барвинок (этот удивительно живучий цветок считается символом жизненной силы — прим. пер.)
Есть множество типов инструкций для OISC. Но в Periwinkle это инструкция move. Просто перемещаете литерал в регистр или значение из одного регистра в другой. Логические и арифметические операции, ветвление и т.д. выполняются с помощью регистров.

Длина инструкции Periwinkle стабильно 40 бит. Шина данных 32 бита.

d2c1704f9a737e3327d43f5618f04867.jpg

У Periwinkle в общей сложности 64 регистра.

98e81e78dc5e2010c74bd500ae7eeee3.jpg

Вот описание некоторых:

Программный счётчик (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? В этих четырёх регистрах есть своеобразный стек, который является на самом деле вычислительным стеком. Когда в стеке вычислений два числа, операция запускается.

7e6a03e447e137c06eb230601809c4dc.jpg

Вот пример для регистра 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). Действительно, она вдохновила меня на работу как первая архитектура, с которой я начал программировать на ассемблере.

© Habrahabr.ru