Легенда
Eщё со школы имею мечту построить собственный компьютер на собственном процессоре. Причём, архитектуру процессора 15 лет безуспешно пытался разработать с нуля. Но дальше RISC-процессора в LogiSim дело не сдвинулось.
Хотя, схему выборки CISC-команды из памяти разработать удалось.
Сейчас разрабатываю эмулятор средствами HTML5 на JavaScript, чтобы никому ничего не нужно было скачивать и работало онлайн.
Если первый эмулятор строился на «switch-case» и это значительно усложняло разработку системы команд, то потом разработал движок с использованием шаблона на принципах «casex» Verilog, где вся система команд описывается в таблице, а скрипт при инициализации парсит её и генерирует массив из тысяч комбинаций команд. Конечно, память расходуется значительно и наблюдаются существенные просадки производительности, но именно такой эмулятор удобен, когда окончательная таблица команд всё ещё не готова и постоянно модифицируется.
Идеология
Опыт коллег (PICO-8, MegaProcessor, MyCPU, Gigatron и т.д…) вдохновляет меня и собственными окольными путями пытаюсь построить собственный процессор без предрассудков: Мне не нужно экономить на простоте, чтобы где-то какой-то проводочек стал короче на миллиметр. И я не тороплюсь запустить архитектуру с колена, чтобы состряпать хоть что-нибудь и это поехало лампочками мигать или фракталы Мандельброта строить…
План прост по-идее, но сложен в перспективе, так как локально планируется переразработать всю линейку архитектуры Intel с нуля. А именно, так как i8080 и i8086 программно никак не совместимы, а система команд обоих оставляет желать лучшего, то поставил план разработать 8-битную архитектуру с расширением до 16 бит или до 32, причём с программной совместимостью в обе стороны…
Ошибки Intel прослеживаются с самого начала:
- На уровне машинного кода i8080 и i8086 абсолютно несовместимы
- На уровне ассемблера совместимость частичная, хоть и заявлялось о ней
- Поддерживаются рудиментарные команды (i8080: 32/3A - STA/LDA; i8086: A0/A1/A2/A3), которые все не перечислишь, но в современном защищённом режиме их наличие - бессмысленно
- Гибриды типа NEC V20 пытались поправить ситуацию, но всё оказалось безнадёжным
Тем самым, был просто взят процессор i8080 во всей его красе и система команд была перераспределена из восьмеричной в шестнадцатеричную и удалены команды с 16-битными константами. И даже можно увидеть ту же систему команд i8080:
Если внимательно присмотреться, то за основу я взял тактику i8080, где инструкцию «MOV M,M» превратили в «HLT»: Всю группу MOV-блока я сдвинул в начало и перегруппировал, чтобы та самая «HLT» заняла код «00». Так как в i8086 код «00» печальным образом занимает команда «ADD», что позднее в Windows разрослось в кучу ловушек, это послужило ценным опытом, как не надо делать на стадии проектирования дешифратора команд в угоду экономии проводочков, так как сейчас дешифрацией x86-кода занимается RISC-ядро и тактические экономические трюки инженеров в 70-х просто навсегда изувечили всю систему команд!
Также и имена регистров переименованы в стиль i8086, что значительно упрощает написание кода под мой процессор в стандартных отладчиках, как та же Visual Studio…
Тем самым, у меня получилось, что:- 00: MOV M,M = HLT
- 11: MOV BH,BH = Prefix BH/BP
- 22:MOV CH,CH = Prefix CH/SI
- 33:MOV DH,DH = Prefix DH/DI
- 44:MOV AL,AL = Prefix SP
- 55:MOV BL,BL = Prefix BL/BX
- 66:MOV CL,CL = Prefix CL/CX
- 77:MOV DL,DL = Prefix DL,DX
Чем для приложения даны 7 префиксов.
Для супервизора имеется «супер-префикс» с кодом «00», так как в «режиме ядра» операция «HLT» не нужна. Чем я убил двух зайцев:- Нету никаких привилегированных команд, которые приложению нельзя использовать. Тем самым, все коды таблицы команд приложение может использовать
- Только в «режиме ядра» этот «супер-префикс» доступен и не болтается мусорным защищённым кодом в системе команд у приложений
Архитектура
Хоть процессор и походит на продвинутый вариант i8080, но это не совсем так.
В режиме супервизора «супер-префикс» 00 даёт доступ к служебному регистру #0, который переключает регистровый файл процессора. Всего предусмотрено до 128 страниц регистрового файла и процессор может выполнять до 128 задач в кольце.
Регистровый файл предусмотрен как внешняя память статического ОЗУ из отдельных регистров. Можно использовать как интегральное ОЗУ одной микросхемой памяти на 32 Кб, а можно и организовать 256 отдельных регистров/ОЗУ на 128 байтов. То есть, в отладочном стенде файл контекста можно организовать из 256 регистров, чтобы в любой момент видеть его содержимое (как в Мегапроцессоре). А в практическом исполнении просто обойтись одной микросхемой памяти…
Как уже понятно, для доступа к РОН хранящихся во внешнем статическом ОЗУ требуются такты. В этом плане процессор работает как тот же 6502 и на простые операции требуется много тактов. С другой стороны, я над этим не беспокоюсь, так как систему кешов и конвейеров никто не отменял, что никак не мешает в перспективе ускорить архитектуру в разы.
(Я уже выше сказал, что не буду совершать ошибки Intel и искать оптимальные пути на этапе проектирования, так как RISC-ядро с лёгкостью может сгладить любые издержки производительности.
А так как это - CISC-процессор, то по-любому архитектура может быть довольно сложной и затратной. Оптимизировать я её не собираюсь!)
Процессор имеет несколько особенностей, которые усложняют его архитектуру, но приближает его к уровню 16-битных.
Так, используются «запретные комбинации» статуса АЛУ, которые никогда в нормальных условиях не встречаются:- ZF PF - SKIP-режим холостого чтения команд
- ZF SF - LOOP-режим выполнения команды несколько раз, один из РОН используется за счётчик
- ZF SF PF - WAIT-режим, тот же LOOP-режим с прерыванием
Так, операции с портами IN/OUT доступны лишь под режимом WAIT и порт, если он существует, вернёт сигнал готовности и прервёт WAIT-цикл. Если же порта аппаратно не существует и сигнала готовности нет, режимом WAIT будет использован один из РОН в качестве счётчика. Программа по обнулённому счётчику может знать, ответил порт или нет…
Так и цикл WAIT+SUB может использоваться как операция деления, так как WAIT прерывается по флагу CF. Аппаратно легко перехватить комбинацию WAIT+SUB или LOOP+ADD внешним сопроцессором и выполнить операции DIV/MUL быстро аппаратно. Тем самым, системой команд предусматривается внешний сопроцессор, но для его операций используются трюковые комбинации стандартных команд.
Режим SKIP упрощает описание «ленивых выражений» и условных кейсов. Если Вы помните DOS с его «INT 21h» с подфункцией через код в AH, то здесь SKIP работает примерно также. Например, комбинация «LOOP 5 + INT 21» не станет вызывать «INT 21» пять раз, а вызовет один раз, но переключится в режим «SKIP 5». Тем самым, если в подпрограмме имеется стопка из «JMP», то будет пропущено 5 «JMP» режимом «SKIP». Это делает решение гораздо изящнее…
Прерывания
Процессор при любом событии всегда переходит в одну заданную точку программы, но с разной SKIP-величиной.
- Обращение к INT 0-79
- Переключение памяти
- Переключение задач
- Запрос к устройству ввода-вывода
- Внешние маскируемые прерывания
- Сигнал немаскируемых прерываний
- Запуск ядра по сигналу RESET
Тем самым, по сигналу СБРОС процессор перепрыгнет в стандартную точку входу и пропустит семь JMP'ов в режиме «SKIP 7». Причём, СБРОС происходит не сразу, а по истечению интервала в служебных регистрах с отсчётом до 65535 тактов. Тем самым, сначала RESET срабатывает как немаскируемое прерывание и управление получает ядро. Если ядро нормально функционирует, то оно предустановит служебные счётчики и предотвратит СБРОС системы. Иначе, спустя указанное число от счётов управление снова получит ядро, но через точку запуска.
Приложения не могут напрямую обращаться к устройствам и операции «WAIT+IN/OUT» генерируют событие #4 ядру, которое уже само должно разбираться.
RISC vs RISC
Хоть процессор разрабатывается по принципам CISC-технологии (городи что хочешь), но он из неё умудрился вылезти!
И я просто переименовал его в RISC, который нужно читать не как «Reduced Instruction Set Computer», а «Rebused Instruction Set Computer». То есть, «Ребусная Система Команд», так как комбинации префиксов достигают уровня головоломки, которую тяжело переварить ассемблером и дизассемблером. Хотя эмулятор их исполняет корректно.
Потому, если Вы любитель простоты, то Вам моя технология не понравится и её можете игнорировать.
С другой стороны, если Вы - любитель головоломок и не прочь сломать мозг машинным кодом, то Вам может понравится сахар…
Сахар в машинном коде
- LOOP n + SKIP => SKIP n ; Цикл из n-раз превращается в игнорирование n-инструкций
- LOOP n + JMP => JMP & SKIP n ; Переход на метку и игнорирование n-инструкций
- LOOP n + CALL => CALL & SKIP n ; Вызов подпрограммы и игнорирование n-инструкций внутри подпрограммы
- LOOP n + INT => INT & SKIP n ; Вызов программного прерывания и игнорирование n-инструкций внутри подпрограммы
- LOOP n + RET => RET & SKIP n ; Возврат из подпрограммы и игнорирование n-инструкций
- LOOP n + MOV R,[IX±offset] => MOV R,[IX+Rn±offset] ; Вместо цикла индексный адрес формируется в индексно-относительный
- LOOP n + MOV [IX±offset],R => MOV [IX+Rn±offset],R ; Вместо цикла индексный адрес формируется в индексно-относительный
- LOOP n + NOP m => NOP n×m ; Команда NOP с задержкой на m-тактов выполняется n-раз
- LOOP n + ADD => MUL n ; Без сопроцессора умножение достигается сложением в цикле
- WAIT n + SUB => DIV n ; Без сопроцессора деление достигается циклом вычитания с прерыванием
- WAIT n + MOV => IN/OUT ; В ожидании команды межрегистровых пересылок превращаются в команды обращения к УВВ с ожиданием готовности
- LOOP n + MOV => MOV ctx ; В цикле команды межрегистровых пересылок обращаются к ячейкам регистрового файла активного контекста
- HLT + MOV M,R => MOV ctrl,R ; Под «супер-префиксом» можно записать регистр страницы контекста и переключить задачу
- LOOP n + HLT => EXIT n ; Запрос системы с выходом и передачей кода результата
- WAIT n + HLT => SYSCALL n ; Обращение к API системы
- LOOP + LOOP => reserved ; Теоретически работает, но назначения и логики не имеет
- LOOP + WAIT => reserved ; Теоретически работает, но назначения и логики не имеет
- WAIT + LOOP => reserved ; Теоретически работает, но назначения и логики не имеет
- WAIT + WAIT => reserved ; Теоретически работает, но назначения и логики не имеет
Можно видеть, что подобные трюки увели концепцию далеко от i8080/z80 и подтянули возможности до i80286…
Система команд напоминает ребус и не понравится тем, кто любит простые решения…
Код:
LOOP 3 ; Режим повтора 3 раза
CALL MyFn ; Повторить вызов нельзя - будет пропуск SubFn0, SubFn1, SubFn2
JMP Error ; Если подфункция отсутствует, RET вернётся на этот JMP
... ; Иначе - нормальное продолжение программы
MyFn:
JMP SubFn0 ; Выполнится по CALL MyFn
JMP SubFn1 ; Выполнится по LOOP 1 + CALL MyFn
JMP SubFn2 ; Выполнится по LOOP 2 + CALL MyFn
ADD AL,DL ; Выполнится по LOOP 3 + CALL MyFn
LOOP 1 ; Пропустим JMP Error
RET
Эмуляция
- Одна из первых версий эмулятора с Монитором в стиле РАДИО-86РК. Автоматически выводится дамп, прыгает шарик с очищением поля и построением рамки, после чего выводится фигурка Тетриса, управляемая клавиатурой
- Другая версия эмулятора. Клавиша F4 запускает эмуляцию. Сначала выводится дамп по «D000,3BF», затем выводится подсказка по директивам, а потом запускается режим эха клавиатуры, которое глючит. Баг очень сложен и связан с неполным пониманием механизма переключения контекста от Супервизора к приложению. Ядро эмуляции спланировано неверно и его нужно полностью перерабатывать. (Если буфер клавиатуры чист, чтение порта приводит к ожиданию циклом Wait без флага CF. Приложению этот флаг нужно передать в контекст, но на уровне JavaScript теряется контроль над синхронностью и приложению возвращаются неверные флаги…)
- В данный период дорабатывается версия эмулятора с путаницей переключения контекстов, так как я занимался внедрением «супер-префикса». Пустяковая доработка заняла три дня отладки и доработок алгоритмов ассемблера, дизассемблера и эмулятора. Но сама проблема пока не решена. Лишь упростились некоторые команды…
Наращивание
Как выше уже сказано, процессор спланирован как восьмибитный, но с перспективой расширения до 16 и 32.
Уже на восьми битах планируются трюки, где АЛУ может работать с 16 и 32 битами. Разница лишь в том, что в 8-битном прототипе на 32-битную операцию может уйти десятки тактов, тогда как в 32-битном прототипе - один такт.
Тем самым, на 8-битном прототипе уже можно разрабатывать 32-битные приложения и запускать их. Разница будет лишь в производительности, так как размер команд останется тем же: В отличии от i8080/z80 здесь нету 16-битных операндов, типа «LD SP,addr». Всё - 8-битное. И на 32-битном прототипе все индексы операндов - один байт.
Способы адресации
Код:
07 :MOV DL,[BX] ; Косвенно-регистровая адресация
67 :MOV DL,CL ; Регистровая адресация
A7 89:MOV DL,0x89 ; Непосредственная адресация
55 A7 89:MOV DL,[BX-119] ; Относительная адресация
E4 55 A7 89:MOV DL,[BX+AL-119] ; Индексная адресация
B8 FE 55 A7 89:MOVX DL,[BX+XX-119] ; Чтение в режиме X по индексу XX
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
0A :ADD AL,[BX] ; Косвенно-регистровая адресация
6A :ADD AL,CL ; Регистровая адресация
AA 89:ADD AL,0x89 ; Непосредственная адресация
55 AA 89:ADD BL,0x89 ; Непосредственная адресация
E4 55 AA 89:ADD4 BL,0x89 ; Сложение в режиме #4
B8 FE 55 AA 89:ADDX BL,0x89 ; Сложение в цикле режима X
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
81 23:PUSH 0x0123 ; Помещение константы в стек
91 23:PUSH 0xF123 ; Помещение константы в стек
55 81 23:PUSH 0x5123 ; Помещение константы в стек
55 91 23:PUSH 0xA123 ; Помещение константы в стек
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
55 A0 23:PUSH [BX+35] ; Относительная адресация
55 B0 23:POP [BX+35] ; Относительная адресация
E4 55 A0 23:PUSH [BX+AL+35] ; Индексная адресация
E4 55 B0 23:POP [BX+AL+35] ; Индексная адресация
B8 FE 55 A0 23:PUSX [BX+XX+35] ; Индексная адресация X по XX
B8 FE 55 B0 23:POPX [BX+XX+35] ; Индексная адресация X по XX
FPGA
Естественно, есть и Verilog-модель, выполняющая несколько команд.
Но, из-за плохого владения принципами Verilog, эскиз модели получился тупиковым и всё нужно переписывать с нуля…
Почему x80?
Почему из всего богатства архитектур я выбрал именно Intel?
Во-первых, с i8080 я знаком с самого детства через ВМ80 в любимом РАДИО-86РК.
Во-вторых, тот же ZX-Spectrum и GameBoy построены на Z80.
В-третьих, система команд CISC более дружелюбна, а i8086 намного легче реализовать, чем изучать 68000, так как с ним я никогда не работал непосредственно.
В-четвёртых, я не так силён в электронике, чтобы городить свой RISC с перспективой конвейерного исполнения 32 команд за такт.
Сотрудничество
Если Вы имеете возможность и способность управиться с Verilog/FPGA/CPLD, буду очень рад формированию хоть какого-то коллектива разработчиков линейки x80, x180, x280, x380…