- PVSM.RU - https://www.pvsm.ru -
Возникал ли у вас когда-нибудь вопрос "как работает процессор?". Да-да, именно тот, который находится в вашем в ПК/ноутбуке/смартфоне. В этой статье я хочу привести пример самостоятельно придуманного процессора с дизайном на языке Verilog. Verilog — это не совсем тот язык программирования, на который он похож. Это — Hardware Description Language. Написанный код не выполняется чем-либо (если вы не запускаете его в симуляторе, конечно), а превращается в дизайн физической схемы, либо в вид, воспринимаемый FPGA (Field Programmable Gate Array).
Дисклеймер: эта статья — результат работы над проектом в университете, поэтому время на работу было ограничено и многие части проекта находятся еще только в начальной стадии разработки.
Прошу обратить внимание, что создаваемый в данной статье процессор имеет мало чего общего с современными широко распространенными процессорами, но его созданием я старался достичь немного другой цели.
Чтобы по-настоящему понимать процесс программирования, надо представлять, как работает каждый из используемых инструментов: компилятор/интерпретатор языка, виртуальная машина, если она есть, промежуточный код, и, конечно же, сам процессор. Очень часто люди, изучающие программирование, долгое время находятся на первой стадии — они думают только о том, как работает язык и его компилятор. Это часто ведет к ошибкам, пути решения которых неизвестны начинающему программисту, потому что он не имеет понятия, откуда растут корни этих проблем. Я сам видел несколько живых примеров, где ситуация была примерно как в описании выше, поэтому я решил попробовать исправить данную ситуацию и создать набор вещей, которые помогут понять начинающим программистам все этапы.
Этот набор состоит из:
Еще раз напоминаю, что данная статья НЕ ОПИСЫВАЕТ НИЧЕГО ПОХОЖЕГО НА СОВРЕМЕННЫЙ РЕАЛЬНЫЙ ПРОЦЕССОР, она описывает модель, которую легко понять без углубления в детали.
Вещи, которые вам понадобятся, если вы хотите запустить все своими руками:
Чтобы запустить симуляцию CPU, необходим ModelSim, который вы можете скачать с сайта Intel.
Для запуска компилятора OurLang необходима Java версии >= 8.
Ссылки на проекты:
https://github.com/IamMaxim/OurCPU [1]
https://github.com/IamMaxim/OurLang [2]
Расширение:
https://github.com/IamMaxim/ourlang-vscode [3]
Для сборки Verilog-части я обычно использую скрипт на bash:
#/bin/bash
vlib work
vlog *.v
vsim -c testbench_1 -do "run; exit"
Но это же можно повторить через GUI.
Для работы с компилятором удобно использовать Intellij IDEA. Главное — следите за тем, какие модули имеет в зависимостях нужный вам модуль. Я не стал выкладывать в открытый доступ готовый .jar, потому что я рассчитываю на то, что читатель будет читать исходный код компилятора.
Запускаемые модули — Compiler и Interpreter. С компилятором все понятно, Interpreter — просто симулятор OurCPU на Java, но мы не будем рассматривать его в этой статье.
Думаю, начать лучше с Instruction Set’а.
Существует несколько архитектур наборов инструкций:
Набор инструкций нашего процессора содержит 30 инструкций [4]
Далее предлагаю взглянуть на реализацию процессора:
Код состоит из нескольких модулей:
RAM — модуль, содержащий непосредственно саму память, а также способ получить доступ к данным в ней.
CPU — модуль, который непосредственно управляет ходом выполнения программы: считывает инструкции, передает контроль нужной инструкции, хранит необходимые регистры (указатель на текущую инструкцию и т.д.).
Практически все инструкции работают только со стаком, так что достаточно лишь выполнить их. Некоторые (например, putw, putb, jmp и jif) имеют дополнительный аргумент в самой инструкции. Им необходимо передать всю инструкцию, чтобы они могли считать необходимые данные.
Вот схема, в общих чертах описывающая ход работы процессора:
Общие принципы устройства программ на уровне инструкций
Думаю, пришло время познакомиться с устройством непосредственно самих программ. Как видно из схемы выше, после выполнения каждой инструкции адрес переходит к следующей. Это дает линейный ход программы. Когда же появляется необходимость нарушить эту линейность (условие, цикл, и т.д.), используются branch-инструкции (в нашем наборе инструкций это jmp и jif).
При вызове функций нам необходимо сохранить текущее состояние всего, и для этого имеются activation record’ы — записи, хранящие эту информацию. Они никак не привязаны к самому процессору или инструкциям, это просто концепт, который используется компилятором при генерации кода. Activation record в OurLang имеет следующую структуру:
Как видно из этой схемы, локальные переменные также хранятся в activation record’е, что позволяет рассчитывать адрес переменной в памяти во время компиляции, а не во время выполнения, и, таким образом, ускоряется выполнение программы.
Для вызовов функции в нашем наборе инструкций предусмотрены способы работы с двумя регистрами, содержащимися в модуле CPU (operation pointer и activation address pointer) – putopa/popopa, putara/popara.
А теперь взглянем на самую близкую к конечному программисту часть — компилятор. В целом, компилятор как программа состоит из 3 частей:
Лексер отвечает за перевод исходного текста программы в лексические единицы, понятные парсеру.
Парсер строит из этих лексических единиц абстрактное синтаксическое дерево.
Компилятор проходит по этому дереву и генерирует какой-то код, состоящий из низкоуровневых инструкций. Это может быть как байт-код, так и готовый к выполнению процессором бинарный код.
В компиляторе OurLang эти части представлены соответственно классами
OurLang находится в зачаточном состоянии, то есть он работает, но в нем пока не так много вещей и не доведена до конца даже Core-часть языка. Но для понимания сути работы компилятора текущего состояния уже достаточно.
Как пример программы для понимания синтаксиса предлагается этот фрагмент кода (он же используется для тестирования функционала):
// single-line comments
/*
* Multi-line comments
*/
function print(int arg) {
instr(putara, 0);
instr(putw, 4);
instr(add, 0);
instr(lw, 0);
instr(printword, 0);
}
function func1(int arg1, int arg2): int {
print(arg1);
print(arg2);
if (arg1 == 0) {
return arg2;
} else {
return func1(arg1 - 1, arg2);
};
}
function main() {
var i: int;
i = func1(1, 10);
if (i == 0) {
i = 1;
} else {
i = 2;
};
print(i);
}
Акцентировать внимание на языке я не буду, оставлю это на ваше изучение. Через код компилятора, естественно ;).
При его написании я пытался сделать self-explaining код, который понятен без комментариев, так что с пониманием кода компилятора проблем возникнуть не должно.
Ну и естественно, самое интересное — писать код, а затем наблюдать за тем, во что он превращается. Благо, компилятор OurLang генерирует assembly-like код с комментариями,
что поможет не запутаться в том, что происходит внутри.
Также рекомендую установить расширение для Visual Studio Code, оно облегчит работу с языком.
Удачи в изучении проекта!
Автор: IamMaxim
Источник [5]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/java/299634
Ссылки в тексте:
[1] https://github.com/IamMaxim/OurCPU: https://github.com/IamMaxim/OurCPU
[2] https://github.com/IamMaxim/OurLang: https://github.com/IamMaxim/OurLang
[3] https://github.com/IamMaxim/ourlang-vscode: https://github.com/IamMaxim/ourlang-vscode
[4] Набор инструкций нашего процессора содержит 30 инструкций: https://github.com/IamMaxim/OurCPU/blob/master/Our%20CPU%20description.md
[5] Источник: https://habr.com/post/430450/?utm_campaign=430450
Нажмите здесь для печати.