Привет, Хабр. Два года назад, как раз перед началом пандемии, я затеял большой проект: построить компьютер, используя только простые логические микросхемы 74 серии и микросхемы памяти. В этой статье я бы хотел кратко рассказать о том, что получилось, и более подробно об основной части – процессоре.
На сегодняшний день можно сказать, что у меня получился полноценный компьютер: на нем можно играть, можно читать и редактировать текстовые файлы на SD-карте, можно считать и даже строить графики. Нельзя только выходить в интернет.
Технические характеристики компьютера получились следующие:
Процессор: 8 бит, 4 регистра, очень урезанный набор инструкций, тактовая частота 1.5 МГц;
Память: 32 кБ ПЗУ и 52 кБ ОЗУ;
Видеокарта: текстовый режим 80x30, 16 цветов (как в CGA), подключение к VGA-монитору;
Внешний накопитель - SD-карта с файловой системой FAT16;
Разъем PS/2 для подключения клавиатуры.
Процессор
В этом разделе я попытаюсь показать ход моих мыслей при проектировании процессора и покажу, что из этого вышло.
Мне хотелось, чтобы получился более-менее полноценный процессор, программировать который было бы не слишком большой болью. То есть, должны быть полновесные 8 бит и небольшой, но не слишком урезанный набор арифметики: обязательно должны быть простые действия вроде сложения-вычитания с переносом и без и все логические операции, но умножение – это уже слишком. Стеком и прерываниями тоже можно смело пожертвовать.
С такими требованиями к арифметике АЛУ легко сделать асинхронным: при подаче значений на входы на выходе сразу появится результат. Чтобы не было слишком много проводов, один вход АЛУ можно привязать к одному конкретному регистру, который обычно называют аккумулятором.
Следующий вопрос – как сделать переходы. Чтобы процессор выполнил инструкцию jmp label
(переход на заданный в инструкции адрес), нужно сначала загрузить адрес в какой-то регистр, а потом уже оттуда передать его в IP. Загружать напрямую в IP нельзя: адрес состоит из двух байт, и когда будет загружен первый байт, мы не сможем загрузить второй, потому что в IP будет уже наполовину новый адрес.
С доступом к памяти та же история: в x86, например, можно сделать так: mov ax, [label]
. Здесь, чтобы загрузить из памяти значение по закодированному в инструкции адресу, этот адрес тоже нужно сначала поместить в невидимый регистр.
Раз для адресации нужен отдельный регистр, почему бы не сделать его доступным программисту? Тогда можно будет явно загружать туда значения и выполнять с ними арифметику, а потом использовать их в качестве адреса перехода и операций с памятью. Назовем этот регистр P. Так как адрес 16-битный, а данные 8-битные, разделим P на две части: PL и PH.
Итак, минимум нужно три регистра, доступных программисту: аккумулятор A для фиксированного подключения к одному из входов АЛУ и пара PL/PH для адресации. Кодировать три регистра в инструкции неудобно: нужно два бита, остается одна неиспользуемая комбинация, поэтому добавим еще один регистр B.
Из-за того, что адрес нужно загружать в P явно, для операций с памятью и перехода потребуется больше одной инструкции. Например, переход:
ldi pl, lo(label) ; загрузка младшего байта адреса в PL
ldi ph, hi(label) ; загрузка старшего байта в PH
jmp ; собственно переход - инструкция без аргументов!
Заметим, что у нас появилось два 16-битных регистра: указатель инструкции IP и указатель адреса P, причем из P нужно уметь передавать значение в IP. Для передачи значения не обязательно копировать его: можно добавить флаг, определяющий, какой из физических регистров будет действовать как IP, а какой как P. При исполнении инструкции перехода этот флаг будет переключаться, и с точки зрения программиста окажется так, что после перехода в P будет адрес возврата! Таким образом получится сделать вызовы функций без использования стека: достаточно будет в начале функции сохранить значение из P, а при возврате считать его и выполнить переход.
Как выглядят пролог и эпилог функции
function:
mov a, ph ; арифметика (включая mov) возможна только между A и другим регистром
mov b, a
mov a, pl
ldi ph, hi(ret_addr)
ldi pl, lo(ret_addr)
st a ; сначала сохраняем младший байт
inc pl ; ret_addr выровнен, поэтому переполнения через 256 не случится
st b
; ... тут сам код функции
ldi ph, hi(ret_addr)
ldi pl, lo(ret_addr)
ld a
inc pl
ld ph ; старший байт можно загрузить сразу в PH
mov pl, a
jmp ; возврат из функции
; в секции данных:
.align 2
ret_addr: res 2 ; резервируем два байта для адреса возврата
Теперь, когда регистры определены, можно нарисовать общую схему процессора.
Здесь мы видим регистры A и B, блок регистров P, содержащий в себе две пары регистров: PL/PH и IP, регистр текущей инструкции IR, регистр флагов и АЛУ (блок в форме надкушенной трапеции).
Для мультиплексирования сигналов на шинах я использую логические сигналы с тремя состояниями. В каждый момент времени на конкретной шине активно только одно устройство, определяющее уровни сигналов, остальные же находятся в состоянии высокого сопротивления.
Красная шина на схеме – это внешняя шина данных, ведущая к памяти и перефирийным устройствам. Данные с нее могут быть напрямую загружены в регистр инструкции IR или через буфер (треугольник под IR на схеме) переданы на внутреннюю шину процессора (зеленая), ведущую на входы всех регистов. АЛУ также выводит свой результат на зеленую шину.
Розовая шина ведет на второй вход АЛУ. Если ни одно из устройств, подключенных к ней, не активно, на этой шине будет ноль благодаря подтягивающим резисторам. Это позволяет использовать ноль вместо регистра в качестве операнда арифметичских инструкций. Например, так: adc a, 0
.
И, наконец, голубая шина, ведущая от блока P наружу – шина адреса. На ней процессор выставляет адрес памяти, чтобы записать или считать данные.
У регистров A и B по два выхода: на внешнюю шину данных и на АЛУ. Таким образом эти регистры могут участвовать в арифметике и быть загруженными в память. Регистры PL и PH не могут быть загружены в память напрямую: это не имеет смысла, ведь они хранят адрес операции с памятью.
Конечно, почти все блоки на этой схеме – это не отдельные микросхемы. Например, для регистра B нужно три микросхемы: собственно восьмибитный регистр 74HC273 и два выходных буфера 74HC244. Для каждой пары регистров из P нужно восемь микросхем: четыре четырехбитных счетчика 74HC161 и четыре буфера 74HC244.
Адресное пространство
Как вы могли заметить, процессор адресует максимум 216 Байт = 64 кБ, но памяти на самом деле больше: 32 кБ ПЗУ и 52 кБ ОЗУ. Такое возможно с помощью переключения банков: по умолчанию в нижние 32 кБ отображается ПЗУ, но если записать нужный бит в регистр конфигурации памяти, можно отобразить туда дополнительную оперативку. Это позволяет делать довольно сложные приложения: из-за крайне низкой плотности кода 32 кБ едва хватает на драйвер файловой системы, поэтому без переключения банков текстовый редактор, например, ну никак не получилось бы написать. А так можно загрузить приложение с SD-карты в нижнюю часть ОЗУ и использовать функции работы с файловой системой из ПЗУ как системные вызовы.
На старшие сегменты адресного пространства отображены видеопамять и регистры периферийных устройств (клавиатуры и SD-карты), а также регистр конфигурации памяти. Видеопамять организована в два отдельных сегмента для цвета и для текста, в отличие от CGA, где цвета перемежаются с символами. Такая организация проще: чтобы вывести строку, можно просто побайтово скопировать ее. Или, например, можно легко очистить часть экрана, оставив информацию о цвете.
Процесс разработки
Для разработки я использовал только свободное ПО (кроме текстового редактора). После определения общей структуры модуля я рисовал желаемые тайминги сигналов и по ним описывал модели и тесты на языке Verilog, которые запускал и проверял с помощью Icarus Verilog и GTKWave. Потом по списку микросхем 7400 серии я выбирал подходящие и смотрел, есть ли они в продаже. Когда микросхемы были выбраны, я переделывал код с использованием моделей конкретных микросхем. Одновременно я рисовал схему в KiCAD. Таким образом получалось полное соответствие между схемой и моделью и можно было быть уверенным (почти), что всё заработает в железе.
Такой подход оправдал себя: в платах почти не было логических ошибок. Возникали другие непредвиденные проблемы: например, благодаря этому проекту я узнал про наводки между соседними дорожками и про отражение высокочастотных сигналов и про то, что будет, если этого не учитывать. Также я узнал, что более быстрая серия микросхем не значит лучшая.
Заключение
Этот пост получился уже довольно длинным, а я многого не рассказал: про видеокарту, про АЛУ, про кодирование инструкций и ассемблер, про общение с PS/2 и SD-картой, а также про программную часть этого карантинного проекта. Если будет интересно, напишу еще посты, а пока можете посмотреть репозиторий.
Комментарии (70)
PereVerden
22.11.2021 21:57+6Хотелось бы чуть больше объяснений на пальцах для дилетантов, местами честно говоря нифига не понял. А вообще класс, знания автора и количество проделанной работы впечатляет !
Жду статью про инструкции и АСУ
ynoxinul Автор
22.11.2021 21:59+1Да, сложно написать о чем-то так, чтобы было всем понятно. Может быть, у вас есть какие-то конкретные вопросы, которые я мог бы прояснить?
PereVerden
22.11.2021 23:57Ну скорее всего, мои вопросы тянуться из очень плохого понимания структуры, логики работы процессора. Когда разговор в статье пошёл о регистрах, лично я сразу потерялся в их предназначении, что утянуло за собой последующие понимание всего.
По этой причине и хочу увидеть статью про АСУ, инструкции и прочих основах.
MilesSeventh
23.11.2021 08:19+8Могу посоветовать nandgame, возможно будет полезно если есть свободное время
Nekun_V
24.11.2021 09:56+1Я же верно понял, что IR содержит текущий опкод? Было бы неплохо детальней пояснить в статье функцию IR, потому что на первый взгляд «регистр текущей инструкции» и «указатель инструкции» одно и то же.
ahdenchik
22.11.2021 22:49+4Напишите про борьбу за частоту?
Я давно наблюдаю за похожим проектом 32-битного процессора, но в нём автор упёрся в 500КГц, и что с этим делать пока не знаетynoxinul Автор
22.11.2021 22:59+3У меня не было никакой борьбы за частоту. Я ни разу не сталкивался с тем, чтобы что-то не работало из-за слишком высокой тактовой частоты. Мне кажется, можно даже немножко разогнать, но я не пробовал: не хочется отлаживать сложноуловимые случайные ошибки. Места, которые, как мне кажется, ограничивают частоту, это:
ПЗУ с программой. По спекам у нее задержка 150 нс, то есть 6.6 МГц. Было бы прикольно сделать переключаемую частоту: по умолчанию медленную, а когда программа прыгает в ОЗУ, увеличить.
Сумматор, выполненный по схеме с переносом через каждый бит. Если грубо оценить задержку, то должна быть в худшем случае тоже порядка пары сотен наносекунд.
И в одном месте у меня есть асинхронная задержка на RC-цепочке, которая фиксит неправильные тайминги доступа к памяти. Это можно исправить, переделав модуль управления.
forthuser
23.11.2021 05:34+2У проекта Gigatron TTL компьютера тактовая частота 6,3МГц при однотактных командах.
На форуме проекта его разгоняли до ~12,5МГц (и вроде до 15МГц) но и потребление с заявленных ~500мА от 5В (2.5W, или 0.5W для 74HCT версии логических микросхем) возрастало существенно на логике другой серии.
P.S. Была статья на Хабре:
Гигатрон — самодельный микрокомпьютер без процессора
Форум Gigatron проекта
Gigatron на сайте https://hackaday.io
drWhy
23.11.2021 10:36«Было бы прикольно сделать переключаемую частоту: по умолчанию медленную, а когда программа прыгает в ОЗУ, увеличить.»
А если сразу плавное управление частотой? Задолго до появления SpeedStep существовал процессор, частота которого подстраивалась под нагрузку.
Dimsml
22.11.2021 23:24+2используя только простые логические микросхемы 74 серии
A 74181 это уже читерство? :)
ynoxinul Автор
22.11.2021 23:35+3Да, читерство :) Да и ее не найти в продаже.
Dimsml
23.11.2021 00:08Да и ее не найти в продаже.
В Чип и Дип вроде можно заказать 74LS181, китайские за 350 рублей и Motorola / Fairchild за 1000+ рублей, но я не имею ни малейшего понятия откуда они их возьмут и возьмут ли вообще. Плюс, непонятно что придёт, особенно от китайцев.
NotebookKiller
23.11.2021 10:03+2Там есть 533/1533ИП3, правда по цене почти 500рэ. Читерство нынче недёшево.
sim2q
23.11.2021 02:47О, это первое на чём я познал как вообще это работает, ещё в планаре была, но быстро наскучило.
nixtonixto
23.11.2021 10:17+1Например, для регистра B нужно три микросхемы: собственно восьмибитный регистр 74HC273 и два выходных буфера 74HC244.
А почему не 74НС573? У неё есть встроенные буферы на выходе. Сброса нет, но по логике и блок-схеме он не нужен.ynoxinul Автор
23.11.2021 10:31+1Блок-схема условная, там многое не обозначено, но сброс – это вторичное. Главная идея в том, что нужно два независимых выхода одного регистра на две разных шины. Поэтому и используется два отдельных буфера.
Кроме того, в 74HC573 входная защелка не по фронту, а по состоянию, это тоже критично.
ScarferNV
23.11.2021 10:54Очень круто, всегда интересно читать про такие самоделки. Есть в планах новые доработки?
ynoxinul Автор
23.11.2021 11:03+1Нет, нету. На самом деле, он у меня уже в таком состоянии с весны лежит, я только иногда какую-нибудь новую программу пишу. Была идея сделать сетевую карту, но пока кажется, что это слишком сложно.
drWhy
23.11.2021 11:17Пробегала статья, в которой несколько малинок были объединены в сеть при помощи единственного компорта.
ScarferNV
23.11.2021 12:13+1Да, использование COM это дедовский способ для построения примитивной сети. А еще можно на основе этого порта сделать беспроводную передачу данных, с помощью светодиодов.
alexzeed
23.11.2021 13:16Хотел написать, что для уарта есть SLIP и можно соорудить SLIP/WiFi gateway из малинки или ESP8266/ESP32 - но сообразил, что у этого компьютера нет уарта :). Тогда пожалуй проще всего подключить ту же ESP32 по параллельной шине, а внутри написать эмуляцию этой шины. Ну или прицепить честно RTL8019AS (бонусом кусочек SRAM внутри) или CS8900, это будет более в стиле "самодельного компьютера". Они обе умеют работать по 8-битной шине данных. Но это будет только проводная сеть, без Wi-Fi.
ynoxinul Автор
23.11.2021 13:22Да, можно много всего наподключать, можно ПЛИС подключить и на ней вообще любую периферию сделать, но это тоже читерство.
alexzeed
23.11.2021 14:45Ну вот мне кажется 10 Мбит NIC чип на 8-битной шине - это достаточно "нечитерский" вариант. Хотя 10Base-T достаточно простой протокол, наверное сетевушка на рассыпухе 74 серии теоретически возможна.
sim2q
24.11.2021 00:52+2Был репитер 10 мегабит коаксиал, внутри почти под две сотни DIP14-16 74xx,74hxx///
3epg
23.11.2021 11:48Только сейчас посмотрел видео из статьи. Обратил внимание, как громко в начале видео работает тактовый генератор. ))
PoetLuchnik
23.11.2021 13:01+2Ранее находил одного человечка который собрал сумматор и назвал это компьтером.
Подумал что эта статья будет подобным клибейтом, но оказалось нет.
Даже больше скажу: теперь мне в голову засела мысль поиметь дома подобный экземпляр.
А теперь странный вопрос: как думаете насколько реально реализовать этот компьютер в Minecraft? (думаю вполне реально по всем параметрам кроме тактовой частоты)
VT100
23.11.2021 17:21В сети пишут, что там уже компов накрафтили: https://www.google.com/search?q=%D0%BA%D0%BE%D0%BC%D0%BF%D1%8C%D1%8E%D1%82%D0%B5%D1%80+%D0%B2+minecraft
PoetLuchnik
23.11.2021 23:05я в курсе, но к сожелению большенство из них это не компьтеры по своей сути, хотя
иногда попадаются реализации реальных (например LC3) или выдуманных (например DjCPU8) процессор и компьютеров целиком, да и сам я однажды практиковал такое.
Но реализовать компьютер из этой статьи будет настоящим мировым рекордом я полагаю.
marcbook
23.11.2021 15:21+2Всегда восхищаюсь подобному энтузиазму, терпению, и умению довести проект до конца! В опросе не хватает вариантов "Обо всем" и "О себе".
Aytuar
23.11.2021 15:42Я бы это сделал на какой нибудь ПЛИС. Всё равно там внутри те же элементы на транзисторах, только намного меньше боли с припоем и проводами как тут https://www.megaprocessor.com
0x9d8e
23.11.2021 17:47Очень понравилась реализация 16-битных адресов. Пожалуй утащу её себе, если получится (всего два регистра, не считая IP и выбирать их нельзя, т.к. аргументов у инструкций тоже нет). А то у меня дальше #00FF прыгнуть можно, только непосредственно с #00FE или #00FF и только на #0100.
upd: Аргументы инструкциям таки придётся добавить.
axe_chita
23.11.2021 20:55Не могу не поделиться ссылкой на канал James Sharman который тоже делает что то подобное.
demsp
23.11.2021 22:53+1Назовем этот регистр P. Так как адрес 16-битный, а данные 8-битные, разделим P на две части: PL и PH.
Для каждой пары регистров из P нужно восемь микросхем: четыре четырехбитных счетчикаА вы не могли бы пояснить зачем там нужны эти счётчики?
ynoxinul Автор
23.11.2021 23:44Адрес инструкции должен инкрементироваться по тактовому сигналу, чтобы процессор смог прочитать следующую инструкцию. Счетчики именно этим и занимаются.
demsp
23.11.2021 23:55Счетчики именно этим и занимаются.
А у меня вот здесь счётчик последовательно перебирает все команды в памяти команд (данные хранятся отдельно от команд). При переходах (джампах) адрес загружается а счётчик.
А у вас, наверное, сначала младший счётик инкременируется?ynoxinul Автор
24.11.2021 01:00+2Четыре микросхемы связаны через перенос (TC-CET) в один 16-битный счетчик.
Схема
Dr_Faksov
24.11.2021 04:36А можно обосновать, почему делая процессор "с нуля" он стал восмибитным? Потому что намертво заложенный в голове шаблон 8 или 16? Почему не 10 или 12 бит?
ynoxinul Автор
24.11.2021 09:54+2Потому что так проще. Шаблон не только в голове, но и в компонентах. Нет регистров или памяти на 10 бит.
Не спорю, было бы интересно сделать что-то нестандартное. Я бы вообще предпочел иметь троичную логику, но ее только на транзисторах делать, потому что готовых микросхем (особенно памяти) не существует.
Dr_Faksov
25.11.2021 06:51-1Как-то я вас не понял. Зачем тогда вообще выбирать сложный путь?
Вы сами жалуетесь что вам нужно две команды на переход. И что из за этого код пухнет так, что надо много памяти чтобы его уместить. И чтобы по этому объёму памяти перемещаться - нужно две команды на переход....
ynoxinul Автор
25.11.2021 11:52+3Я вас тоже не очень понял. Мне хотелось сделать функциональный компьютер за конечное время, вот я и сделал. В чем претензия вообще?
Dr_Faksov
26.11.2021 07:01Да нет у меня к вам никаких претензий! Могу даже извинится если чем-то обидел.
Просто глядя на ваш проект, не могу понять в чём его особенность? Проверить возможность создания компа классической архитектуры на классической элементной базе?
Мне вспоминаются братья Стругацкие, которые устами одного из своих персонажей говорили что неинтересно решать задачи, про которые точно известно что они имеют решение. Куда интереснее решать задачи про которые точно известно что они решения не имеют.
ynoxinul Автор
26.11.2021 10:27+2Ну мне с детства хотелось собрать свой компьютер с нуля, и сейчас я до этого дорос. Ни на какие прорывные технические решения не претендую. Я получил массу удовольствия от такого вот велосипедостроения.
Alex_ME
24.11.2021 11:18Очень круто, сам пару лет назад загорелся идеей сделать такое, но потом как-то забил. Пишите про всё!
DerBad
24.11.2021 13:14Впечатлён) Именно такие «очумельцы» и вернут нас к цивилизации после апокалипсиса… если что))
vipassa
25.11.2021 10:37Отличная идея для любительского радиоконструктора с целью изучения основ устройства процессоров и компмьютеров
AlexanderS
26.11.2021 11:53В опросе — типичная манипуляция. Пункт «Ни о чем» есть, а «Обо всём» — нет! А выбрать можно только один пункт…
drWhy
26.11.2021 12:11Может у автора времени нет писать обо всём. Хотя подумать стоит, из песочницы сотню кармы не каждая статья автору даёт, а тут КМК Технотекст плачет по таким статьям, если всё подробно описать.
Когда-то наблюдал авиационный бортовой компьютер — собранные в брикет керамические подложки с бескорпусными микросхемами — очень напоминает описанный компьютер, только там микросхемы поплотнее стояли.AlexanderS
26.11.2021 13:40Может у автора времени нет писать обо всём.
Моя задача проголосовать, а уж как он будет выбирать о чём дальше писать — его дело)
VT100
Зело прельстиво. Пишите обо всём, я считаю.