Как написать виртуальный процессор
Инструмент проще, чем машина. Зачастую инструментом работают руками, а машину приводит в действие паровая сила или животное.
Компьютер тоже можно назвать машиной, только вместо паровой силы здесь электричество. Но программирование сделало компьютер таким же простым, как любой инструмент.
Процессор — это сердце/мозг любого компьютера. Его основное назначение — арифметические и логические операции, и прежде чем погрузиться в дебри процессора, нужно разобраться в его основных компонентах и принципах их работы.
Два основных компонента процессора
Что делать дальше?
В попытках найти ответ на сей вопрос, я раз пять переписал эмулятор с C на C++, и обратно.
Однако главные цели можно выделить уже сейчас:
- Прикрутить нормальные прерывания (Вместо простого вызова функции и запрета на прием других прерываний сделать вызов функции и добавление новых прерываний в очередь).
- Прикрутить устройства, а также способы общения с ними, благо опкодов может быть 256.
- Научить себя не писать всякую ересь на хабр процессор работать с определенной тактовой частотой в 200 МГц.
Команды (инструкции)
Команды — это фактические действия, которые компьютер должен выполнять. Они бывают нескольких типов:
- Арифметические: сложение, вычитание, умножение и т. д.
- Логические: И (логическое умножение/конъюнкция), ИЛИ (логическое суммирование/дизъюнкция), отрицание и т. д.
- Информационные: move , input , outptut , load и store .
- Команды перехода: goto , if . goto , call и return .
- Команда останова: halt .
Прим. перев. На самом деле все арифметические операции в АЛУ могут быть созданы на основе всего двух: сложение и сдвиг. Однако чем больше базовых операций поддерживает АЛУ, тем оно быстрее.
Инструкции предоставляются компьютеру на языке ассемблера или генерируются компилятором высокоуровневых языков.
В процессоре инструкции реализуются на аппаратном уровне. За один такт одноядерный процессор может выполнить одну элементарную (базовую) инструкцию.
Группу инструкций принято называть набором команд (англ. instruction set).
Заключение
Надеюсь, что кому-нибудь эта "статья" станет полезной, кого то подтолкнет на написание чего-то похожего.
Мои куличики можно посмотреть на github.
Также, о ужас, у меня есть ассемблер для старой версии этого эмулятора (Нет, даже не пытайтесь, эмулятор как минимум пожалуется на неправильный формат ROM)
Здравствуйте! В этой статье я расскажу какие шаги нужно пройти для создания простого процессора и окружения для него.
Для начала нужно определиться с тем, каким будет процессор. Важны такие параметры как:
- Размер машинного слова и регистров(разрядность/"битность" процессора)
- Машинные команды (инструкции) и их размер
Архитектуры процессоров можно разделить по размеру инструкций на 2 вида (на самом деле их больше, но другие варианты менее популярны):
Основное их отличие в том, что RISC процессоры имеют одинаковый размер инструкций. Их инструкции простые и выполняются сравнительно быстро, тогда как CISC процессоры могут иметь разный размер инструкций, некоторые из которых могут выполняться достаточно продолжительное время.
Я решил сделать RISC процессор во многом похожий на MIPS.
Я это сделал по целому ряду причин:
- Довольно просто создать прототип такого процессора.
- Вся сложность такого вида процессоров перекладывается на такие программы как ассемблер и/или компилятор.
Вот основные характеристики моего процессора:
- Машинное слово и размер регистров — 32 бита
- 64 регистра (включая счетчик команд)
- 2 типа инструкций
Register type(досл. Регистровый тип) выглядит вот так:
Особенность таких инструкций заключается в том, что они оперируют с тремя регистрами.
Immediate type(досл. Немедленный тип):
Инструкции этого типа оперируют с двумя регистрами и числом.
OP — это номер инструкции, которую нужно выполнить (или же для указания, что эта инструкция Register type).
R0, R1, R2 — это номера регистров, которые служат операндами для инструкции.
Func — это дополнительное поле, которое служит для указания вида Register type инструкций.
Imm — это поле куда записывается то значение, которое мы хотим явно предоставить инструкции в качестве операнда.
Полный список инструкций можно посмотреть в github репозитории.
Вот лишь пару из них:
NOR это Register type инструкция, которая делает логическое ИЛИ НЕ на регистрах r1 и r2, после записывает результат в регистр r0.
Для того, чтобы использовать эту инструкцию нужно изменить поле OP на 0000 и поле Func на 0000000111 в двоичной системе счисления.
LW это Immediate type инструкция, которая загружает значение памяти по адресу r1 + n в регистр r0.
Для того, чтобы использовать эту инструкцию в свою очередь нужно изменить поле OP на 0111, а в поле IMM записать число n.
После создания ISA можно приступить к написанию процессора.
Для этого нам нужно знание какого нибудь языка описания оборудования. Вот некоторые из них:
- Verilog
- VHDL (не путать с предыдущим!)
Я выбрал Verilog, т.к. программирование на нем было частью моего учебного курса в университете.
Для написания процессора нужно понимать логику его работы:
- Получение инструкции по адресу Счетчика команд (PC) инструкции
- Выполнение инструкции
- Прибавление к Cчетчику команды размера выполненной инструкции
И так до бесконечности.
Получается нужно создать несколько модулей:
Разберем по отдельности каждый модуль.
Начнем писать!
Реализация основных структур процессора
Набор регистров. Регистров всего четыре, но ситуацию улучшает то, что таких наборов в процессоре целых два. Переключение происходит при помощи инструкции XCR .
Флаги. В отличии от DCPU-16, V16 имеет условные переходы, вызовы подпрограмм и возвраты оттуда же. На данный момент процессор имеет 8 флагов, 5 из которых — флаги условий.
Собственно, сам процессор. Здесь также описана таблица адресов прерываний, что вполне можно назвать дескрипторами и найти ещё одну отсылку на x86.
Операнд. При получении значений, нам необходимо сначала прочитать, затем изменить, а затем записать значение туда, откуда мы его взяли.
Функции для работы со структурами
Когда все структуры описаны, всплывает необходимость в функциях, которые наделят эти структуры магической силой угашенного кода.
Также я не упомянул большое перечисление с кодами операций, но это необязательно и необходимо только для понимания, что происходит во всей этой каше.
Функция tick()
Также здесь присутствуют вызовы static-функций, предназначенных только для вызова из tick() .
Память (ОЗУ)
ОЗУ (оперативное запоминающее устройство, англ. RAM) — это большая группа этих самых регистров, соединённых вместе. Память у такого хранилища непостоянная и данные оттуда пропадают при отключении питания. ОЗУ принимает адрес ячейки памяти, в которую нужно поместить данные, сами данные и флаг записи/чтения, который приводит в действие триггеры.
Прим. перев. Оперативная память бывает статической и динамической — SRAM и DRAM соответственно. В статической памяти ячейками являются триггеры, а в динамической — конденсаторы. SRAM быстрее, а DRAM дешевле.
Тактирование процессора
Быстродействие компьютера определяется тактовой частотой его процессора. Тактовая частота — количество тактов (соответственно и исполняемых команд) за секунду.
Частота нынешних процессоров измеряется в ГГц (Гигагерцы). 1 ГГц = 10⁹ Гц — миллиард операций в секунду.
Чтобы уменьшить время выполнения программы, нужно либо оптимизировать (уменьшить) её, либо увеличить тактовую частоту. У части процессоров есть возможность увеличить частоту (разогнать процессор), однако такие действия физически влияют на процессор и нередко вызывают перегрев и выход из строя.
С чего начать?
А начать нужно, разумеется, с описания процессора.
В самом начале, я планировал написать эмулятор DCPU-16, но таких чудес на просторах Интернета хватает с лихвой, поэтому я решил остановиться только на "слизывании" самого основного с DCPU-16 1.1.
Выполнение инструкций
Инструкции хранятся в ОЗУ в последовательном порядке. Для гипотетического процессора инструкция состоит из кода операции и адреса памяти/регистра. Внутри управляющего устройства есть два регистра инструкций, в которые загружается код команды и адрес текущей исполняемой команды. Ещё в процессоре есть дополнительные регистры, которые хранят в себе последние 4 бита выполненных инструкций.
Ниже рассмотрен пример набора команд, который суммирует два числа:
- LOAD_A 8 . Это команда сохраняет в ОЗУ данные, скажем, . Первые 4 бита — код операции. Именно он определяет инструкцию. Эти данные помещаются в регистры инструкций УУ. Команда декодируется в инструкцию load_A — поместить данные 1000 (последние 4 бита команды) в регистр A .
- LOAD_B 2 . Ситуация, аналогичная прошлой. Здесь помещается число 2 ( 0010 ) в регистр B .
- ADD B A . Команда суммирует два числа (точнее прибавляет значение регистра B в регистр A ). УУ сообщает АЛУ, что нужно выполнить операцию суммирования и поместить результат обратно в регистр A .
- STORE_A 23 . Сохраняем значение регистра A в ячейку памяти с адресом 23 .
Вот такие операции нужны, чтобы сложить два числа.
Все данные между процессором, регистрами, памятью и I/O-устройствами (устройствами ввода-вывода) передаются по шинам. Чтобы загрузить в память только что обработанные данные, процессор помещает адрес в шину адреса и данные в шину данных. Потом нужно дать разрешение на запись на шине управления.
У процессора есть механизм сохранения инструкций в кэш. Как мы выяснили ранее, за секунду процессор может выполнить миллиарды инструкций. Поэтому если бы каждая инструкция хранилась в ОЗУ, то её изъятие оттуда занимало бы больше времени, чем её обработка. Поэтому для ускорения работы процессор хранит часть инструкций и данных в кэше.
Если данные в кэше и памяти не совпадают, то они помечаются грязными битами (англ. dirty bit).
Хранение информации — регистры и память
Как говорилось ранее, процессор выполняет поступающие на него команды. Команды в большинстве случаев работают с данными, которые могут быть промежуточными, входными или выходными. Все эти данные вместе с инструкциями сохраняются в регистрах и памяти.
Регистры
Регистр — минимальная ячейка памяти данных. Регистры состоят из триггеров (англ. latches/flip-flops). Триггеры, в свою очередь, состоят из логических элементов и могут хранить в себе 1 бит информации.
Прим. перев. Триггеры могут быть синхронные и асинхронные. Асинхронные могут менять своё состояние в любой момент, а синхронные только во время положительного/отрицательного перепада на входе синхронизации.
По функциональному назначению триггеры делятся на несколько групп:
- RS-триггер: сохраняет своё состояние при нулевых уровнях на обоих входах и изменяет его при установке единице на одном из входов (Reset/Set — Сброс/Установка).
- JK-триггер: идентичен RS-триггеру за исключением того, что при подаче единиц сразу на два входа триггер меняет своё состояние на противоположное (счётный режим).
- T-триггер: меняет своё состояние на противоположное при каждом такте на его единственном входе.
- D-триггер: запоминает состояние на входе в момент синхронизации. Асинхронные D-триггеры смысла не имеют.
Для хранения промежуточных данных ОЗУ не подходит, т. к. это замедлит работу процессора. Промежуточные данные отсылаются в регистры по шине. В них могут храниться команды, выходные данные и даже адреса ячеек памяти.
Принцип действия RS-триггера
Декодер
Декодер это тот блок, который отвечает за декодирование инструкций. Он указывает какие операции нужно выполнить АЛУ и другим блокам.
Например, инструкция addi должна сложить значение регистра $zero(Он всегда хранит 0) и 20 и положить результат в регистр $t0.
На этом этапе декодер определяет, что эта инструкция:
- Immediate type
- Должна записать результат в регистр
И передает эти сведения следующим блокам.
После управление переходит в АЛУ. В нем обычно выполняются все математические, логические операции, а также операции сравнения чисел.
То есть, если рассмотреть ту же инструкцию addi, то на этом этапе происходит сложение 0 и 20.
Регистровый файл
Регистровый файл предоставляет доступ к регистрам. С его помощью нужно получать значения каких то регистров, или изменять их.
В моем случае у меня 64 регистра. В один из регистров записывается результат операции над двумя другими, так что мне нужно предоставить возможность изменять только один, а получать значения из двух других.
Другие
По мимо вышеперечисленных блоков, процессор должен уметь:
- Получать и изменять значения в памяти
- Выполнять условные переходы
Тут и там можно увидеть как это выглядит в коде.
После написания процессора нам нужна программа, которая бы преобразовывала текстовые команды в машинный код, чтобы не делать этого вручную. Поэтому нужно написать ассемблер.
Я решил реализовать его на языке программирования Си.
Так как мой процессор имеет RISC архитектуру, то для того, чтобы упростить себе жизнь, я решил спроектировать ассемблер так, чтобы в него можно было легко добавлять свои псевдоинструкции(комбинации из нескольких элементарных инструкций или из других псевдоинструкций).
Можно реализовать это с помощью структуры данных, хранящей в себе тип инструкции, ее формат, указатель на функцию, которая возвращает машинные коды инструкции, и ее название.
Обычная программа начинается с объявления сегмента.
Для нас достаточно двух сегментов .text — в котором будет храниться исходный код наших программ — и .data — в котором будет хранится наши данные и константы.
Инструкция может выглядеть вот так:
Сначала указывается название инструкции, потом операнды.
В .data же указываются объявления данных.
Объявление должно начинаться с точки и названия типа данных, после же идут константы или аргументы.
Удобно парсить (сканировать) ассемблер файл в таком виде:
- Сначала сканируем сегмент
- Если это .data сегмент, то мы парсим разные типы данных или .text сегмент
- Если это .text сегмент, то мы парсим команды или .data сегмент
Для работы ассемблеру нужно проходить исходный файл 2 раза. В первый раз он считает по каким смещениям находятся ссылки (они служат для), они обычно выглядят вот так:
А во второй проход можно уже и генерировать файл.
В дальнейшем, можно запускать выходной файл из ассемблера на нашем процессоре и оценивать результат.
Также готовый ассемблер можно использовать в Си компиляторе. Но это уже позже.
В дополнение к стандартным алгоритмам программирования, программатор имеет виртуальный процессор, позволяющий пользователю самостоятельно написать произвольный алгоритм работы с микросхемой. На практике это означает, что программатор может работать с любой микросхемой, которая проходит по электрическим характеристикам и "попадает" в ключи (Vcc и Vpp) в панельке программатора. Система команд разработана с учетом особенностей программирования микросхем, и позволяет писать простой и эффективный код, обеспечивающий высокую скорость работы с микросхемой.
Исходный код программы это обычный текстовый файл, который содержит последовательность команд, описывающих алгоритм чтения или записи конкретной микросхемы. С помощью встроенного в оболочку компилятора этот файл проверяется на наличие ошибок, компилируется и загружается по определенному адресу в блок параметров микросхемы. В начале каждого цикла компьютер передает в программатор код режима работы и загружает блок параметров микросхемы. В соответствии с заданными параметрами, программатор устанавливает конфигурацию выводов панельки и начинает считывать и исполнять команды загруженного скрипта. После завершения цикла программатор отключает питание микросхемы и передает в компьютер результаты работы.
Скрипт представляет собой сильно упрощенный специализированный микроассемблер, заточенный под ПО программатора и ориентированный на работу с микросхемами. Кроме простейших арифметико-логических операций, операций проверки условий или управления выводами панельки, система команд содержит команды чтения/записи байта, слова или блока данных в последовательном или параллельном режимах, команды обработки считанных данных, а также комбинированные команды, выполняющие несколько операций.
Под скрипт в параметрах микросхемы отведено 192 байта. Это может показаться ничтожно мало, но сейчас в программаторе более 200 скриптов и пока этого объема вполне хватает. Вот два примера скрипта, читающих содержимое микросхемы W25Qxx:
Скрипт позволяет обратиться к первым 128 ячейкам в блоке параметров ($00..$7F), где находятся основные параметры микросхемы. Начиная с адреса $80 размещаются шины адреса и данных и сам скрипт. Доступ из скрипта к этой области закрыт. В памяти программатора блок параметров микросхемы расположен по адресу $80. Поэтому, в командах адрес ячейки необходимо увеличивать на $80. Например, для проверки считанной сигнатуры, расположенной в блоке параметров по адресу $00..$01, используются команды JRN $80,Label и JRN $81,Label.
Описание. В каждой строке должна находится только одна команда. В начале строки может находится метка, за ней через разделитель должна следовать команда и далее один или два операнда, в зависимости от команды. Регистр значения не имеет. В качестве разделителей можно использовать один или несколько пробелов, знак табуляции или запятую. Используемый компилятор не имеет препроцессора, поэтому какие-либо операции над операндами недопустимы.
Комментарий - ';' может находиться в любом месте строки. Все, что стоит после него компилятором игнорируется. Другие типы комментариев, такие как '//', '<>', не поддерживаются.
Метка должна начинаться с начала строки. Знак ':' в конце метки должен присутствовать обязательно. После метки должен быть разделитель: пробел или знак табуляции.
Команда не должна находится в начале строки. Если метки нет, то перед командой должен быть разделитель. После команды, через разделители, могут присутствовать один или два операнда.
Операнд. В качестве операндов используются обычные числа, зарезервированные или назначенные константы. Все числа должны записываться только в шестнадцатеричном формате, начинаться с символа '$' и содержать 2 знака. Запись '$3' - недопустима, правильная запись - '$03'.
Константы. Для лучшей читаемости скрипта вместо числовых значений можно использовать зарезервированные или назначенные константы. Чтобы назначить константу, в начале скрипта необходимо ввести что-то типа: '.WE =$4B'
Константа | Описание |
---|---|
ADR_L, ADR_M, ADR_H | Счетчик адреса микросхемы. Автоматически инициализируется в начале каждого цикла. Для инкремента и проверки окончания цикла используются команды JNA (JumpIfNextAddr) и JLA (JumpIfLastAddr). |
FIN_L, FIN_M, FIN_H | Конечный адрес микросхемы. Используется при проверке конца цикла. Мало когда нужен, но вдруг потребуется. |
DATAL, DATAH | Регистры временного хранения данных, считанных из буфера командами LDB и LDW. |
BYTEL, BYTEH | Регистры временного хранения данных, считанных из микросхемы. |
TIMER | Регистр таймера. Период инкремента ~3msec. Максимальное значение TIMER ~380msec. Сброс таймера - CLT. Проверка таймера - JTO. |
FLAGM, FLAGW, FLAGS, FLAGE | Флаги микросхемы, флаги режимов работы, флаги программатора, флаги ошибок. Описание этих флагов есть в описании блока параметров микросхемы. Менять значения этих флагов, кроме флага ошибок, во время работы, не рекомендуется. |
REG_1.. REG_5 | Адреса регистров [$18,$19,$1A,$1B,$1C], обеспечивающих установку логических уровней и чтение состояния выводов панельки. REG_1=$1C, REG_2=$19, REG_3=$18, REG_4=$1A, REG_5=$1B. Для программатора V5.8T: REG_6=$1D. Привязка регистров к выводам панельки показана на рисунке. |
VCP_1.. VCP_5 | Адреса регистров [$10,$11,$12,$13,$14], обеспечивающих подачу Vpp и Vcc на выводы панельки. Привязка регистров к выводам панельки показана на рисунке. |
PULSE | Ячейка блока параметров ($10). Длительность импульса записи. |
NLOCK | Ячейка блока параметров ($17). Количество бит защиты. |
CFG_L, CFG_H | Ячейки блока параметров ($24, $25). Конфигурационное слово микросхемы. |
REG_CS, REG_DWR, REG_DRD, REG_CLK | Ячейки блока параметров ($2C..$2F). Используются для подпрограмм последовательного ввода-вывода. Адреса регистров: CS, Dat_WR, Dat_RD, CLK. |
* Vcc на 22 выводе есть только в программаторах V5.7T
Для правильной работы скрипта необходимо задать начальное состояние выводов микросхемы (Log 0/1, GND, VCC), которое будет подано на микросхему в момент включения питания и которое обеспечивает вход микросхемы в режим программирования. Чтобы не повредить находящуюся в микросхеме информацию во время включения или выключения питания, микросхема должна находится в неактивном состоянии. Для микросхем с параллельным доступом тут же можно назначить шину адреса и данных. Для микросхем с последовательным доступом, линии ввода-вывода назначаются на вкладке Управляющие сигналы.
Для работы с любой микросхемой используются всего четыре режима работы: чтение, запись, стирание и установка защиты. Соответственно, в каждом скрипте обязательно должны присутствовать метки READ, PROG, ERAS и LOCK. В начале каждого цикла командами BGR или BGW необходимо подать питание на микросхему. При необходимости, можно выполнить проверку готовности или считать ID микросхемы. Затем для режимов READ и PROG открыть файл командой FLP.
В режиме READ данные считываются с микросхемы и обрабатываются, в зависимости от выбранного режима работы (записываются в буфер, сравниваются с данными из буфера, проверяются на чистоту и участвуют в подсчете контрольной суммы). Для чтения 8 или 16 битных микросхем с параллельной шиной данных используются команды PRB и PRW. Для чтения микросхем с последовательной шиной - SRL и SRR. Обработка данных производится командами CPB (Compare Byte) и CPW (Compare Word). Для обработки конфигурации микросхем используются команды CCB, CCW и CCP. Для организации цикла используются команды проверки адреса: JNA Label и JLA Label.
В режиме PROG данные считываются из буфера (команды LDB - Load Byte и LDW - Load Word) и записываются в микросхему. Для микросхем с параллельной шиной данных, для установки данных используются команды PWB и PWW, после чего формируется импульс записи. Для микросхем с последовательной шиной - SWL и SWR. Цикл организуется командами JNA Label и JLA Label.
В режиме ERAS производится стирание микросхемы, если в микросхеме есть такой режим. Если такого режима работы нет, то после метки ERAS должна следовать команда END.
Режим LOCK обеспечивает установку или снятие защиты, запись Fuses или конфигурационных регистров. Если режим не используется, то после метки LOCK должна следовать команда END.
Пример скрипта для работы с W27C512 в корпусе PLCC-32 через переходник TSU-D32/PL32:
Где то утверждают что это по сути и есть ядро (или поток ядра). И например при технологии Hyper-Threading система определяет физическое ядро как два виртуальных.
Кто то утверждает что виртуальный процессор может эмулироватся программно молл виртуальный процессор можно сравнить с операционной системой. Поток по отношению к нему выступает как процесс, подобно тому, как сам виртуальный процессор является процессом с точки зрения операционной системы.
Еще говорят что при создании виртуальной машины можно для нее выделять виртуальные процессоры
ТАК ЧТО ТАКОЕ ВИРТУАЛЬНЫЙ ПРОЦЕССОР
Арифметико-логическое устройство
Это устройство, как ни странно, выполняет все арифметические и логические операции, например сложение, вычитание, логическое ИЛИ и т. п. АЛУ состоит из логических элементов, которые и выполняют эти операции.
Большинство логических элементов имеют два входа и один выход.
Ниже приведена схема полусумматора, у которой два входа и два выхода. A и B здесь являются входами, S — выходом, C — переносом (в старший разряд).
Схема арифметического полусумматора
Устройство управления
Устройство управления (УУ) помогает процессору контролировать и выполнять инструкции. УУ сообщает компонентам, что именно нужно делать. В соответствии с инструкциями он координирует работу с другими частями компьютера, включая второй основной компонент — арифметико-логическое устройство (АЛУ). Все инструкции вначале поступают именно на устройство управления.
Существует два типа реализации УУ:
- УУ на жёсткой логике (англ. hardwired control units). Характер работы определяется внутренним электрическим строением — устройством печатной платы или кристалла. Соответственно, модификация такого УУ без физического вмешательства невозможна.
- УУ с микропрограммным управлением (англ. microprogrammable control units). Может быть запрограммирован для тех или иных целей. Программная часть сохраняется в памяти УУ.
УУ на жёсткой логике быстрее, но УУ с микропрограммным управлением обладает более гибкой функциональностью.
Поток инструкций
Современные процессоры могут параллельно обрабатывать несколько команд. Пока одна инструкция находится в стадии декодирования, процессор может успеть получить другую инструкцию.
Однако такое решение подходит только для тех инструкций, которые не зависят друг от друга.
Если процессор многоядерный, это означает, что фактически в нём находятся несколько отдельных процессоров с некоторыми общими ресурсами, например кэшем.
Довольно давно имелось желание написать эмулятор какого-нибудь процессора.
А что может быть лучше, чем изобрести велосипед?
Имя велосипеду — V16, от склеивания слова Virtual и, собственно, разрядности.
1 ответ 1
Это не термин, а обычное словосочетание. Виртуальный процессор - это нечто, что выглядит как работоспособный процессор, но процессором не является. Точное значение зависит от контекста.
Так, когда-то давно никто не слышал про многоядерные процессоры, а когда было нужно - ставили несколько процессоров. И когда многоядерные процессоры появились - то в целях обратной совместимости каждое ядро стало "представляться" операционной системе как отдельный процессор. То есть у вас на материнке стоит один проц - а ОС пишет, что их два. Никакого второго процессора реально не существует, он - виртуальный. Точнее, оба ядра называют виртуальными процессорами, потому что они одинаковые.
Существуют эмуляторы процессоров. Например, вы можете скачать эмулятор NES и поиграть на нем в старые игры. Эти игры написаны для процессора Ricoh 2A03, но играть в них вы будете на процессоре семейства Intel. Однако большинство игр подмены не заметят, поскольку эмулятор реализует все нужные инструкции. В этом смысле эмулятор NES содержит в себе виртуальный процессор.
Внутри виртуальной машины работает реальная ОС, называемая "гостевой". Эта ОС, точно так же, как и при работе вне виртуальной машины, пытается работать с "железом". Но у виртуальной машины все "железо" - виртуальное (за редкими исключениями): если вы, к примеру, зайдёте в Диспетчер устройств на виртуалке с виндой - вы не увидите ничего похожего на реальные устройства. Процессоры у виртуальной машины - тоже виртуальные.
Архитектура
Память и порты
- V16 адресует 128Kb (65536 слов) оперативной памяти, которая также может использоваться как буферы устройств и стек.
- Стек начинается с адреса FFFF, следовательно, RSP имеет стандартное значение 0xFFFF
- Портов ввода-вывода V16 имеет 256, все они имеют длину в 16 бит. Чтение и запись из них осуществляется через инструкции IN b, a И OUT b, a .
Регистры
V16 имеет два набора регистров общего назначения: основной и альтернативный.
Работать процессор может только с одним набором, поэтому между наборами можно переключаться при помощи инструкции XCR .
Инструкции
Все инструкции имеют максимальную длину в три слова и полностью определяются первым
Первое слово делится на три значения: младший байт — опкод, старший байт в виде двух 4-битных значений — описание операндов.
Прерывания
Прерывания здесь — не более чем таблица с адресами, на которые процессор дублирует инструкцию CALL . Если значение адреса равно нулю, то прерывание не делает ничего, просто обнуляет флаг HF.
Диапазон значений | Описание |
---|---|
0x0. 0x3 | Регистр как значение |
0x4. 0x7 | Регистр как значение по адресу |
0x8. 0xB | Регистр + константа как значение по адресу |
0xC | Константа как значение по адресу |
0xD | Константа как значение |
0xE | Регистр RIP как значение только для чтения |
0xF | Регистр RSP как значение |
Пример псевдокода и слов, в которые все это должно странслироваться:
Cycles (Такты)
V16 может выполнять одну инструкцию за 1, 2 или 3 такта. Каждое обращение к оперативной памяти это один отдельный такт. Инструкция это не такт!
Читайте также: