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



Пример программ
Код процессора реализован как ужасная куча логических вентилей, которые включаются и выключаются, но он работает. Я прогнал модульные тесты, а все мы знаем, что модульные тесты — неопровержимое доказательство, что программа работает.
Код обрабатывает ввод с клавиатуры и отображает текст на дисплее, используя кропотливо созданный набор глифов для профессионального шрифта, который я назвал Daniel Code Pro. Единственный чит: чтобы взять ввод с клавиатуры и вывести результат, мне пришлось подключить каналы через GLFW, но в остальном это полностью программная симуляция электросхемы.
Я даже на��исал грубый ассемблер, который на многое открыл глаза, мягко говоря. Он не идеален. На самом деле даже немного дерьмовый, но он показал мне проблемы, которые другие люди уже решили много-много лет назад.
Но зачем ты это делаешь?
«Тринадцатилетние дети собирают процессоры в Minecraft. Позови, когда сможешь сделать настоящий CPU из телеграфных реле»
Моя ментальная модель устройства CPU застряла на уровне учебников по информатике для начинающих. Процессор для эмулятора Gameboy, который я написал в 2013 году, на самом деле не похож на современные CPU. Даже если эмулятор — это просто конечный автомат (машина состояний), он не описывает состояния на уровне логических вентилей. Почти всё можно реализовать с помощью только оператора
switch и сохраняя состояние регистров.Я хочу лучше разобраться, как всё устроено, потому что не знаю, например, что такое кэш L1/L2 и конвейеризация и я не совсем уверен, что понимаю статьи об уязвимостях Meltdown и Spectre. Кто-то сказал, что они оптимизируют код таким образом, чтобы использовать кэш процессора, но я не знаю, как это проверить, кроме как поверить на слово. Я не совсем уверен, что означают все инструкции x86. Не понимаю, как люди отправляют задачи на GPU или TPU. И вообще, что такое TPU? Я не знаю, как использовать SIMD-инструкции.
Всё это построено на фундаменте, который нужно усвоить в первую очередь. Это значит вернуться к основам и сделать что-то простое. В вышеупомянутой книге Кларка Скотта описан простейший компьютер. Вот поч��му я начал с него.
Слава Скотту! Он работает!
Компьютер Скотта — это 8-разрядный процессор, подключённый к 256 байтам ОЗУ, все они подключены через 8-разрядную системную шину. У него 4 регистра общего назначения и 17 машинных инструкций. Кто-то сделал визуальный симулятор для веба: это действительно здорово. Страшно подумать, сколько времени потребовалось, чтобы отследить все состояния схемы!

Схема со всеми компонентам процессора Скотта. Копирайт 2009-2016. Зигберт Фильбингер и Джон Кларк Скотт
Книга сопровождает вас по маршруту от скромных логических вентилей до битов в памяти и регистров, а затем продолжает наслаивать компоненты, пока вы не получите что-то похожее на схему выше. Очень рекомендую прочитать книгу, даже если вы уже знакомы с концепциями. Только не версию Kindle, потому что диаграммы иногда трудно увеличить и разобрать на экране «читалки». По-моему, это многолетняя проблема Kindle.
Мой компьютер отличается от версии Скотта разве что тем, что я обновил его до 16 бит, чтобы увеличить объём доступной памяти, ведь хранение только глифов для таблицы ASCII занимает большую часть 8-битной машины Скотта, оставляя совсем мало места для полезного кода.
Мой процесс разработки
В целом, разработка шла по такой схеме: чтение текста, изучение диаграмм, а затем попытка реализовать их на языке программирования общего назначения и определённо не использовать никаких специализированных инструментов для проектирования интегральных схем. Я написал симулятор на Go просто потому, что немного знаком с этим языком. Скептики могут сказать: «Болван! Неужели ты не мог изучить VHDL или Verilog, или LogSim, или ещё что-то. Но к тому моменту я уже написал свои биты, байты и логические вентили и погрузился слишком глубоко. Может, в следующий раз я выучу эти языки и пойму, сколько времени потратил впустую, но это мои проблемы.
В большой схеме в компьютере просто передаётся куча булевых значений, поэтому подходит любой язык, который дружит с булевой алгеброй.
Наложение схемы на эти булевы значения помогает нам (программистам) вывес��и смысл, а самое главное — решить, какой порядок байтов будет использовать система, и убедиться, что все компоненты передают данные по шине в правильном порядке.
Это было очень трудно реализовать. Ради смещения я выбрал представление с обратным порядком байтов, но при тестировании ALU никак не мог понять, почему выходят неправильные цифры. Мой кот услышал много, очень много непечатных выражений.
Разработка шла не быстро: возможно, она заняла около месяца-двух моего свободного времени. Но когда только процессор успешно выполнил операцию
Всё шло своим чередом, пока дело не дошло до ввода-вывода. Книга предлагала дизайн системы с простой клавиатурой и интерфейсом дисплея, чтобы вводить данные в машину и выводить результат. Ну, мы уже зашли так далеко, нет смысла останавливаться на полпути. Я поставил цель реализовать набор на клавиатуре и отображение букв на дисплее.
Периферия
В качестве аппаратного интерфейса между CPU и внешним миром периферийные устройства используют шаблон адаптера. Наверное, несложно догадаться, что этот шаблон позаимствован из проектирования программного обеспечения.

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

Вероятно, это самая сложная часть, по крайней мере, самая громоздкая. Трудно писать на ассемблере с таким ограниченным набором инструкций, а на моём грубом ассемблере ещё хуже, потому что вы не можете обматерить никого, кроме себя.
Самой большой проблемой стало жонглирование четырьмя регистрами, отслеживать их, вытаскивая данные из регистров и временно сохраняя их в памяти. В процессе я вспомнил, что у процессора 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-V. Вероятно, лучше начать с ранних процессоров RISC, чтобы понять их происхождение. У современных процессоров гораздо больше функций: кэши и прочее, я хочу разобраться в них. Там нужно многое изучить.
Пригодятся ли эти знания на моей основной работе? Возможно, пригодятся, хотя вряд ли. В любом случае, мне это нравится, так что неважно. Спасибо за чтение!