Как я уже сказал в предыдущей статье, я собираюсь проектировать новый процессор в Logisim. А для этого необходимо составить план, определиться с целями и поставить ограничения. А ещё я опишу более подробно мой предыдущий процессор, вдруг кому-то будет интересно.
Так как CPU-0 я уже давно удалил, за неимением иных скриншотов поставлю те же самые.
Итак, если вы посмотрите на общий вид, то заметите, что процессор будто-бы состоит из двух частей. Левую часть я называю основным блоком логики, потому что именно там находятся регистры, АЛУ и прочие части процессора. Справа мы видим множество дорожек и элементов AND и OR. Это декодер команд. Он получает на вход опкод и текущий такт и подаёт на другие элементы управляющие сигналы. У каждой инструкции есть опкод (код операции), и они могут занимать более одного такта. Именно поэтому декодер такой огромный. Да, признаю, я сделал его очень неэффективно, новый процессор будет иметь декодер получше.
Большой прямоугольник в центре блока логики — ОЗУ. Да, это был мой первый процессор, и я не придумал ничего лучше чем встроить ОЗУ прямо в него. В новом процессоре память будет отдельным внешним элементом. Это позволит легче расширять её.
С регистрами тоже беда. Грубо говоря, у программиста есть прямой доступ только к одному регистру. Вот список всех регистров CPU-0:
- PC, или программный счётчик. Хранит адрес следующей инструкции в списке на выполнение. Доступ к нему осуществляется только через специальные команды. То есть, запись в этот регистр осуществляется командой JMP, а чтение — командой ADR.
- AR, или регистр адреса. Значение из этого регистра является адресом для работы с памятью. Доступа к нему нет, значения записываются в него автоматически. Например, при извлечении инструкции из памяти в него записывается значение из программного счётчика, а при записи данных в память в него записывается адрес, переданный в качестве аргумента инструкции. Чтение из него невозможно.
- IR, или регистр команд. Хранит инструкцию, исполняемую в данный момент. Доступа к нему нет, в конце исполнения команды в него автоматически записывается значение из памяти по адресу из PC.
- IPR, или регистр адреса обработчика прерываний. Как ясно из названия, хранит адрес, по которому переходит управление в случае возникновения прерывания. Запись инструкцией IPS. Чтение недоступно.
- ACC, или аккумулятор. Хранит значение, с которым процессор работает в данный момент. Фактически, единственный регистр, к которому программист имеет полный доступ.
- B, или... ну, просто B. Вспомогательный регистр. Хранит второе значение для операций в АЛУ. Доступа к нему нет, всё записывается автоматически.
- OUT, или регистр выхода. Постоянно выдаёт своё значение на выход процессора. Записть командой SND, чтение невозможно.
- RET, или регистр возврата. Хранит адрес, по которому нужно вернуться из обработчика прерываний. Доступа к нему нет.
Ну и, собственно говоря, это всё. Есть ещё пара регистров-счётчиков, которые считают такты для команд, которые занимают больше одного такта. Как видите, очень минималистично.
А вот набор инструкций:
- NOP - ничего не делает.
- LDA [адрес] - загружает в аккумулятор значение из ячейки памяти по указанному адресу.
- LDI [число] - загружает в аккумулятор указанное число.
- STA [адрес] - сохраняет значение из аккумулятора в память по указанному адресу.
- ADD/SUB [адрес] - прибавляет/вычитает число из ячейки памяти по указанному адресу и сохраняет результат в аккумулятор.
- ADI/SBI [число] - прибавляет/вычитает указанное число и сохраняет результат в аккумулятор.
- JMP [число] - переходит к указанному адресу.
- JPM [адрес] - переходит к адресу, указанному в ячейке памяти с указанным адресом (косвенная адресация).
- JEZ/JMZ [число/адрес] - переходит прямо/косвенно, если стоит флаг нуля.
- JPC/JMC [число/адрес] - переходит прямо/косвенно, если стоит флаг переполнения.
- AND/OR/XOR [адрес] - выполняет указанную операцию между аккумулятором и значением из памяти по указанному адресу и сохраняет результат в аккумулятор.
- NOT - инвертирует значение аккумулятора.
- SND - выдать значение аккумулятора на порт выхода.
- SDM [адрес] - выдать на порт выхода значение из памяти по указанному адресу.
- SDI [число] - выдать указанное значение на порт выхода.
- RCV - принять число с порта входа и сохранить в аккумулятор.
- RCM [адрес] - принять число с порта входа и сохранить в память по указанному адресу.
- IRE [число] - установить флаг разрешения обработки прерываний в значение младшего бита числа.
- IPS [адрес] - установить адрес обработчик прерываний.
- WAT - остановить выполнение команд и ждать внешнего прерывания, после чего продолжить.
- IRS - отправить сигнал прерывания для внешних устройств на порт выхода.
- IRT - вызвать внутреннее прерывание.
- SFT [число] - выполнить сдвиг значения аккумулятора на значение четырёх старших бит числа. Направление сдвига определяется младшим битом числа.
- ADR - сохранить адрес выполняемой сейчас команды в аккумулятор.
- RET - выход из обработчика прерываний.
- HLT - завершение выполнения и полная остановка.
Опять же, не очень много. Но я считал, что этого вполне хватит.
Что касается прерываний, то оно здесь одно. Процессор просто может получить сигнал прерывания и, если флаг разрешения прерываний установлен, перейти по адресу обработчика прерываний. Стека здесь тоже нет. Можно, конечно, сделать программный, но с прерываниями он работать не будет, потому что к регистру возврата нет доступа. Из операций, только простейшие сложение/вычитание и логические по типу И, ИЛИ, НЕ и сдвигов.
Именно поэтому я и хочу сделать новый процессор. Этот вышел слишком простым. Итак, обозначу свои цели и ограничения:
- Не использовать туннели. Да, снова. Мне кажется, что создать схему с использованием туннелей слишком легко.
- Разделить процессор на блоки и подсхемы. Я считаю, что подсхемы всё-таки можно использовать. Вы бы знали, как мне надоело искать способ протянуть дорожку через 3 шины и кучу других дорожек и элементов. А подсхемы позволят лучше всё организовать. Я думаю, это не так уж и "читерно".
- Память должна подключаться как внешнее устройство.
- Несколько режимов работы. Пока что я думаю о двух режимах: реальный и защищённый. Несколько уровней привилегий. Опять же, я думаю, что хватит двух: пользователь и ядро. И, соответственно, системы защиты. Например, чтобы код с привилегиями пользователя не мог менять системные регистры или обращаться к памяти других программ. И да, виртуальная страничная память тоже нужна.
- Больше регистров. И регистров общего назначения, и системных. Чтобы было больше функций, например, тот же стек, для него нужен регистр указателя стека.
- Более гибкий набор инструкций.
- Нормальный блок прерываний, позволяющий работать с несколькими прерываниями с разным приоритетом.
- Больше инструкций и операций над данными, хотя бы умножение и деление.
- Более простой интерфейс. Имеются в виду входы и выходы. С теми работать было практически невозможно.
Ну вот, собственно, пока что всё. В следующей статье начну разработку набора инструкций, я думаю, именно с этого и нужно начинать. Перед тем, как проектировать процессор, нужно знать, что он должен уметь делать.