Хабрахабр

[Перевод] Я не знал, как работают процессоры, поэтому написал программный симулятор

Несколько месяцев назад меня вдруг поразила мысль, что я понятия не имею о принципах работы компьютерного железа. Я до сих пор не знаю, как работают современные компьютеры.

И мне захотелось реализовать всё это в коде. Я прочитал книгу «Но откуда он знает?» Кларка Скотта с детальным описанием простого 8-битного компьютера: начиная с логических вентилей, ОЗУ, транзисторов процессора, заканчивая арифметико-логическим устройством и операциями ввода-вывода.

Но мне недостаточно текстового описания. Хотя я не настолько интересуюсь физикой микросхем, но книга просто скользит по волнам и красиво объясняет электросхемы и как биты перемещаются по системе — от читателя не требуется знание электротехники. Так я начал реализацию схем в коде. Я должен видеть вещи в действии и учиться на своих неизбежных ошибках. Он простой и он вычисляет. Путь оказался тернист, но поучителен.
Результат моей работы можно посмотреть в репозитории simple-computer: простом вычислителе.


Пример программ

Я прогнал модульные тесты, а все мы знаем, что модульные тесты — неопровержимое доказательство, что программа работает. Код процессора реализован как ужасная куча логических вентилей, которые включаются и выключаются, но он работает.

Единственный чит: чтобы взять ввод с клавиатуры и вывести результат, мне пришлось подключить каналы через GLFW, но в остальном это полностью программная симуляция электросхемы. Код обрабатывает ввод с клавиатуры и отображает текст на дисплее, используя кропотливо созданный набор глифов для профессионального шрифта, который я назвал Daniel Code Pro.

Он не идеален. Я даже написал грубый ассемблер, который на многое открыл глаза, мягко говоря. На самом деле даже немного дерьмовый, но он показал мне проблемы, которые другие люди уже решили много-много лет назад.

«Тринадцатилетние дети собирают процессоры в Minecraft. Позови, когда сможешь сделать настоящий CPU из телеграфных реле»

Процессор для эмулятора Gameboy, который я написал в 2013 году, на самом деле не похож на современные CPU. Моя ментальная модель устройства CPU застряла на уровне учебников по информатике для начинающих. Почти всё можно реализовать с помощью только оператора switch и сохраняя состояние регистров. Даже если эмулятор — это просто машина состояний, он не описывает состояния на уровне логических вентилей.

Кто-то сказал, что они оптимизируют код таким образом, чтобы использовать кэш процессора, но я не знаю, как это проверить, кроме как поверить на слово. Я хочу лучше разобраться, как всё устроено, потому что не знаю, например, что такое кэш L1/L2 и конвейеризация и я не совсем уверен, что понимаю статьи об уязвимостях Meltdown и Spectre. Не понимаю, как люди отправляют задачи на GPU или TPU. Я не совсем уверен, что означают все инструкции x86. Я не знаю, как использовать SIMD-инструкции. И вообще, что такое TPU?

Это значит вернуться к основам и сделать что-то простое. Всё это построено на фундаменте, который нужно усвоить в первую очередь. Вот почему я начал с него. В вышеупомянутой книге Кларка Скотта описан простейший компьютер.

Компьютер Скотта — это 8-разрядный процессор, подключённый к 256 байтам ОЗУ, все они подключены через 8-разрядную системную шину. У него 4 регистра общего назначения и 17 машинных инструкций. Кто-то сделал визуальный симулятор для веба: это действительно здорово. Страшно подумать, сколько времени потребовалось, чтобы отследить все состояния схемы!

Копирайт 2009-2016.
Схема со всеми компонентам процессора Скотта. Зигберт Фильбингер и Джон Кларк Скотт

Очень рекомендую прочитать книгу, даже если вы уже знакомы с концепциями. Книга сопровождает вас по маршруту от скромных логических вентилей до битов в памяти и регистров, а затем продолжает наслаивать компоненты, пока вы не получите что-то похожее на схему выше. По-моему, это многолетняя проблема Kindle. Только не версию Kindle, потому что диаграммы иногда трудно увеличить и разобрать на экране «читалки».

Мой компьютер отличается от версии Скотта разве что тем, что я обновил его до 16 бит, чтобы увеличить объём доступной памяти, ведь хранение только глифов для таблицы ASCII занимает большую часть 8-битной машины Скотта, оставляя совсем мало места для полезного кода.

В целом, разработка шла по такой схеме: чтение текста, изучение диаграмм, а затем попытка реализовать их на языке программирования общего назначения и определённо не использовать никаких специализированных инструментов для проектирования интегральных схем. Я написал симулятор на Go просто потому, что немного знаком с этим языком. Скептики могут сказать: «Болван! Неужели ты не мог изучить VHDL или Verilog, или LogSim, или ещё что-то. Но к тому моменту я уже написал свои биты, байты и логические вентили и погрузился слишком глубоко. Может, в следующий раз я выучу эти языки и пойму, сколько времени потратил впустую, но это мои проблемы.

В большой схеме в компьютере просто передаётся куча булевых значений, поэтому подходит любой язык, который дружит с булевой алгеброй.

Наложение схемы на эти булевы значения помогает нам (программистам) вывести смысл, а самое главное — решить, какой порядок байтов будет использовать система, и убедиться, что все компоненты передают данные по шине в правильном порядке.

Ради смещения я выбрал представление с обратным порядком байтов, но при тестировании ALU никак не мог понять, почему выходят неправильные цифры. Это было очень трудно реализовать. Мой кот услышал много, очень много непечатных выражений.

Но когда только процессор успешно выполнил операцию $2 + 2 = 5$, я был на седьмом небе от счастья. Разработка шла не быстро: возможно, она заняла около месяца-двух моего свободного времени.

Книга предлагала дизайн системы с простой клавиатурой и интерфейсом дисплея, чтобы вводить данные в машину и выводить результат. Всё шло своим чередом, пока дело не дошло до ввода-вывода. Я поставил цель реализовать набор на клавиатуре и отображение букв на дисплее. Ну, мы уже зашли так далеко, нет смысла останавливаться на полпути.

В качестве аппаратного интерфейса между CPU и внешним миром периферийные устройства используют шаблон адаптера. Наверное, несложно догадаться, что этот шаблон позаимствован из проектирования программного обеспечения.


Как адаптеры ввода-вывода подключаются к окну GLFW

На самом деле я просто вытащил большую часть кода из своего эмулятора и немного изменил его, чтобы каналы Go работали как сигналы ввода/вывода. С таким разделением оказалось довольно просто подключить клавиатуру и дисплей к окну под управлением GLFW.

Трудно писать на ассемблере с таким ограниченным набором инструкций, а на моём грубом ассемблере ещё хуже, потому что вы не можете обматерить никого, кроме себя. Вероятно, это самая сложная часть, по крайней мере, самая громоздкая.

В процессе я вспомнил, что у процессора Gameboy есть регистр указателя стека для удобства выгрузки и загрузки регистров. Самой большой проблемой стало жонглирование четырьмя регистрами, отслеживать их, вытаскивая данные из регистров и временно сохраняя их в памяти. К сожалению, у этого компьютера нет такой роскоши, поэтому приходилось постоянно вручную перемещать данные в память и обратно.

Без этого доступны вызовы только на один уровень в глубину. Я решил потратить время только на одну псевдоинструкцию CALL, чтобы вызвать функцию, а затем вернуться к точке.

В книге обсуждаются шаги, необходимые для реализации прерываний, но это серьёзно усложняет схему. Кроме того, поскольку машина не поддерживает прерывания, пришлось реализовать ужасный код опроса состояния клавиатуры.

Это не совсем операционная система, но даёт понимание, что делает простая ОС. Но хватить ныть, я всё-таки написал четыре программы, и большинство из них используют какой-то общий код для рендеринга шрифтов, ввода с клавиатуры и т. д.

Самая сложная часть программы text-writer — правильно рассчитать, когда перейти к новой строке или что происходит, когда вы нажимаете клавишу Enter. Это было нелегко.

main-getInput: CALL ROUTINE-io-pollKeyboard CALL ROUTINE-io-drawFontCharacter JMP main-getInput

Основной цикл программы text-writer

Зато понял, сколько труда требует разработка текстовых редакторов и насколько это утомительно. Я не удосужился реализовать клавишу Backspace и клавиши-модификаторы.

Это был весёлый и очень полезный для меня проект. В разгар программирования на ассемблере я почти забыл о логических вентилях, работающих внизу. Я поднялся на верхние уровни абстракции.

Хотя этот процессор очень прост и далёк от CPU в моём ноутбуке, но мне кажется, что проект многому меня научил, в частности:

  • Как биты перемещаются по шине между всеми компонентами.
  • Как работает простой ALU.
  • Как выглядит простой цикл Fetch-Decode-Execute.
  • Что машина без регистра указателя стека и концепции стека — отстой.
  • Что машина без прерываний тоже отстой.
  • Что такое ассемблер и что он делает.
  • Как периферийные устройства взаимодействуют с простым процессором.
  • Как работают простые шрифты и как отображать их на дисплее.
  • Как может выглядеть простая операционная система.

Так что дальше? В книге говорится, что никто не производил таких компьютеров с 1952 года. Это значит, что мне придётся изучить материал за последние 67 лет. Это займёт меня на какое-то время. Я вижу, что руководство по x86 составляет 4800 страниц: вполне достаточно для приятного, лёгкого чтения перед сном.

Не знаю, посмотрим. Может, я немного побалуюсь с операционной системой, языком C, убью вечер с набором для сборки PiDP-11 и паяльником, а потом заброшу это дело.

Вероятно, лучше начать с ранних процессоров RISC, чтобы понять их происхождение. Если серьёзно, то я думаю исследовать архитектуру RISC, возможно, RISC-V. Там нужно многое изучить. У современных процессоров гораздо больше функций: кэши и прочее, я хочу разобраться в них.

Возможно, пригодятся, хотя вряд ли. Пригодятся ли эти знания на моей основной работе? Спасибо за чтение! В любом случае, мне это нравится, так что неважно.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть