В наше время большинство портативных устройств работает на базе достаточно мощных микроконтроллеров, которые способны запускать даже интерпретируемый код на Lua/Python. Чего уж там говорить — даже современная кофеварка или умный электрочайник может быть в разы мощнее оригинального IBM-PC, не говоря уже о автомобильных бортовых компьютерах, которые зачастую мощнее топовых ПК из начала нулевых. Но давайте вспомним конец 90-х и начало 2000-х, когда разработка собственной электроники была практически недоступна рядовому пользователю, а микроконтроллеры программировались в основном только на ассемблере. Недавно я нашёл некоторую информацию о том, какой процессор вероятно использовался в таких знакомых нам приставках Brick Game, которые мы называли «Тетрисами»! Более того, мне удалось найти полный даташит с описанием всех модулей этого процессора, который гордо можно назвать «система на кристалле». Какой была разработка микроэлектроники в 90-х? Читайте в статье!
❯ Немного о «Тетрисе»
Пожалуй, Тетрис или Brick Game был одной из самых популярных портативных игровых консолей в странах СНГ. Появившись где-то в конце 90-х, этот гаджет быстро стал бестселлером среди детишек благодаря наличию сразу нескольких игр, полноценного ЖК-экрана, звука и невероятной дешевизны. Не знаю, сколько Тетрис стоил в момент выхода, но в нулевых цена на него была крайне низкой — около 100-200 рублей в зависимости от корпуса. Типичный школяр мог накопить на собственный Тетрис за несколько недель, что делало его самым доступным игровым девайсом на рынке.
Конечно же, на рынке уже были различные консоли с гораздо более богатым функционалом — например GameBoy и даже GameBoy Color с цветным дисплеем, а люди, родившиеся в конце 90-х или начале 2000-х уже застали PlayStation Portable с реально крутой 3D-графикой и телефоны с хорошим игровым потенциалом — как, например, SE K500i. Однако цена на них была непозволительной роскошью для небогатых семей: PSP стоила 250$ (около 7-8 тысяч рублей по тому курсу), плюс каждый UMD-диск с игрой стоил около 1.000 рублей, GameBoy были относительно редкими в России, а телефоны — это всё же прерогатива более юных ребят, да и в нулевых далеко не всем перепадал крутой K500i — чаще всего покупали телефон попроще типа Siemens A55 (грузчика помним?) или Motorola C350 (а мотоциклиста?). Поэтому тетрисы оставались чуть ли не единственным средством развлечения у небогатых ребят.
Ощутимым плюсом было и то, что Тетрис работал от батареек: они были не слишком дорогими в то время, а если носить с собой в кармане парочку, то можно не бояться, что консоль сядет в долгой дороге и продолжать себя забавлять, да и сам Тетрис работал довольно долго, мне хватало на неделю игры (может и меньше). Несмотря на низкое разрешением всего в 10x20 пикселей, Тетрис обладал достаточно большим монохромным дисплеем без подсветки, на котором было комфортно играть.
Ещё одним немаловажным плюсом консоли была возможность «кооперативной» игры и эдакого азарта: будучи неискушенными детьми, многие из нас пытались поставить рекорды и выбить как можно больший счёт в каждой из доступных игр. Чем больше счёт, тем ты круче среди друзей!
Но что же у Тетриса «под капотом»? На чём он работал внутри? Недавно я нашёл информацию о том, что потенциально в Тетрисе могла использоваться 4х-битная система на чипе Holtek HT1130, которая использовалась в самой разной носимой электроники: от часов на батарейках, до полноценных игровых консолей. Причём я ничуть не преувеличиваю, это действительно SoC: уже в 90-х, тайваньская компания смогла объединить звуковой модуль, контроллер ЖК-дисплея, ввод/вывод и таймер в один чип! Однако тут важно понять, что 100% сказать, на чём работал Тетрис, нельзя — процессор спрятан под компаундом и у него нет корпуса с маркировкой, лишь «голый» кристалл. Тем не менее, мы можем предположить, что это был один из чипов Holtek и посмотреть, на чём же работала портативная электроника тех лет поближе!
Заранее прошу прощения за отсутствие нормальных фотографий. Под рукой у меня не оказалось «старого» Тетриса, а на новодельных показывать как-то не очень.
На данный чипсет есть «утёкший» в сеть даташит с полным описанием регистров микроконтроллера и его ТТХ. Чип был спроектирован так, чтобы не требовать практически никакой обвязки в виде конденсаторов/резисторов и других SMD-элементов — он работал фактически напрямую от пальчиковых батареек и его легко было развести на плате даже начинающему инженеру. Микроконтроллер стабильно оперировал при напряжении от 2.4в до 3.3в, что позволяло просто вставить две последовательно соединенные AA или AAA батарейки с напряжением 1.5-1.6в и получить необходимое питание для работы всей «приставки».
❯ Вычислительное ядро
Саму систему на кристалле можно разделить на несколько соединенных модулей в один чип. Основным, конечно же, является 4х-битное вычислительное ядро неизвестной архитектуры, которое компания Holtek разработала сама или лицензировала как IP-ядро у другой компании для использования в собственном чипе (как, например, MediaTek лицензирует у ARM ядра Cortex). Система команд, по крайней мере, описание мнемоник ассемблера в даташите наводят на мысли о некоторой схожести с микроконтроллером Intel 8051 (однако 8051 был 8-битным) и в целом, напоминают типичную интеловскую архитектуру из 80х. Однако только по мнемоникам точно определить архитектуру невозможно: здесь есть «проприетарные» команды типа SOUND и TIMER.
Чип работает на частоте 1мгц от встроенного тактового генератора, большинство команд выполняется за один такт, максимум — два. Если говорить совсем грубо, то даже ATMega328 в Arduino условно в 16-раз мощнее HT1130, хотя это совсем некорректное сравнение.
Длина машинного слова HT1130 — 4 бита, что отсылает нас в начало 70х годов, если мы говорим о компьютерах. Это означает, что процессор «аппаратно» мог выполнять операции только с числами от 0 до 16, хотя при программной реализации мог пересчитывать хоть 32х-битные числа. Ширина шины данных — 12 бит, что позволяло адресовать вплоть до 4Кб встроенной ROM-памяти. Кроме того, в МК было встроено 128 ячеек оперативной памяти (или 64 байта), где в 00H..7FH хранились временные данные программы (например, позиция танчиков на экране) и с E0H..FFH хранился «буфер» кадра, который определял текущую на экране. Также у микроконтроллера были следующие регистры:
- R0-R4 — регистры общего назначения. Пары из регистров используются для адресации памяти.
- ACC — регистр-аккумулятор, который хранит результаты текущей операции.
- PC — указатель на текущую инструкцию в ROM, которую выполняет процессор.
- Стековый регистр — судя по всему, «невидимая» связка регистров, которую процессор использует для хранения PC при вызове функций. Ограничен максимум двумя адресами, что не даёт возможность писать программы с вложенностью более двух функций.
С стековым регистром всё интересно получается. В отличии от привычных нам архитектур, HT1130 не хранит в этом регистре указатель на память, он сам по себе как-бы является стеком. Пример допустимого и недопустимого кода:
int sum(int a, int b) {
return a + b;
}
void a() {
sum(2, 3); // Можно
}
void c() {
a(); // Нельзя, поскольку sum заместит указатель PC из функции c на функцию a.
}
Выбор 4х-битного процессора очевиден — они очень недорогие в производстве и достаточно простые. Примеры использования 4х-битных архитектур можно найти даже в советских играх: например, в игре «Волк и яйца» (клоне Nintendo Game & Watch) использовалась «микроЭВМ» КБ1013ВК1-2.
В встраиваемой и переносной электронике, 4х-битные вычислительные ядра продолжают использоваться и сейчас: в калькуляторах, в пультах для управления техникой, в часах. Связано это с простой и дешевизной подобных решений, да и если честно, реализация этих устройств была готова ещё в прошлом веке. Зачем дополнительно тратить деньги на R&D существующих решений? :)
❯ Графика
HT1130 специально разрабатывался для переносимых устройств с новомодными LCD-дисплеями. В те годы было нормой, когда на дисплейной матрице не было собственного контроллера с распространенным интерфейсом по типу 8080 или MIPI: частенько, драйвер дисплея либо выделялся в отдельный чип, либо реализовывался прямо в системе на кристалле. У Brick Game был дисплей разрешением в 10x20 пикселей, причём кастомизированный — с «захардкоженными» значками и сегментными индикаторами:
Работа с дисплеем была не особо сложной: один сегмент памяти был отведен специально под эдакий «фреймбуфер» — всё, что мы записывали туда, контроллер дисплея моментально отображал на нашу ЖК-матрицу. Поскольку дисплей был одноцветным, без градаций цвета, память была организована 1 бит — 1 пиксель. Работать со всем этим было как-то так:
MOV A, 0 ; Первая часть адреса (если я не напутал endianness)
MOV R1, A
MOV A, 0b0111 ; Вторая часть адреса. Не удивляйтесь, что по факту получается 224 при 128 байт ОЗУ - часть адресного пространства была как-бы зарезервирована
MOV R0, A
MOV A, 1 ; Закрасим первый пиксель в строке
MOV [R1R0], A ; И запишем это значение в ОЗУ
Частичным доказательством того, что этот чип мог использоваться в Brick Game — это то, что компания Holtek производила готовые «игры на кристалле» — вероятно, уже запрограммированные с завода HT1130 с определенными играми:
Примечательно, что даташит на готовые игры датируются ноябрём 1998 года, в то время как даташит на HT1130 — 1999. Если у вас появился Тетрис раньше этого времени — напишите пожалуйста в комментариях!
❯ Звук
Помимо этого, чипсет имел собственный генератор звука, или как вы вероятно подумаете — «пищалку». В чип (или SDK, если честно, не особо понятно из даташита) была уже встроена звуковая библиотека — причём половину из них как раз таки для игр, что ещё раз косвенно подтверждает догадки об использовании этого чипа в «Тетрисе». Всего поддерживалось до 16 «каналов», в которых было по 3 тональности. В звуковой библиотеке содержались следующие звуки:
- Шумы
- Мелодии
- Выстрелы
- Будильники
Управлять звуковым трактом было очень просто — буквально несколькими командами на ассемблере. Команда SOUND <номер канала> выбирала один из предопределенных звуков (причем не совсем ясно, где они хранились — возможно в ROM), а команда SOUND ONE/LOOP воспроизводила его в одном из режимов — один раз или повторяющийся. SOUND OFF же выключала звук совсем. Как-то так:
play:
SOUND A
SOUND ONE
SOUND OFF
loop:
INC A ; Увеличиваем значение в аккумуляторе
JC play ; Если флаг переноса установлен, то наш "таймер" как-бы переполнился и пора снова проиграть звук
CLC ; А если нет, то снова выполняем, пока не переполнится, эта операция очищает флаг переноса
JMP loop;
Совсем немудрено, согласитесь? :)
❯ Порты ввода/вывода и кнопки
Кроме этого, HT1130 имел несколько портов ввода-вывода, которые, однако, назвать GPIO нельзя — было 12 портов для вывода, которые могли читать логический уровень (для кнопок), и 4 пина, который мог задавать логический уровень (например, управлять вибромотором или светодиодом). Настройки портов задавались с помощью флагов: можно было настроить встроенные pull-up резисторы и они могли вызывать прерывания при переходе из высокого уровня в низкий.
Выходной порт мог быть сконфигурирован под тип выхода — CMOS или NMOS. Работа с портами шла с помощью команд IN и OUT — как в x86, а обрабатывать их можно было как-то так:
loop:
IN A,0b00110010 ; Загрузить в аккумулятор значение порта PM
AND A,0b0001 ; Отсекаем биты состояний других кнопок и проверяем, нажата ли первая кнопка?
JNZ A, btn_pressed ; Если в аккумуляторе не 0 (а значит кнопка нажата) - то переходим на другую метку
JMP loop ; Если нет - то проверяем по новой
btn_pressed:
SOUND 0
SOUND ONE ; Воспроизвести звук при нажатии
❯ Таймер
В чипсете есть встроенный таймер — ну его ж не просто так для часов использовали. :) Основная суть работы аппаратных таймеров заключается в том, что его тактирует какой-либо внешний тактовый генератор с определенным делителем, в нашем случае — от 1 до 6 относительно системного генератора частоты (т.е 1мгц) и с определенной частотой он делает инкремент внутреннего регистра. Как только регистр переполняется — он очищается и вызывается соответствующее прерывание в основном процессоре.
Это позволяет регулировать скорость работы таймера, а посчитать количество тиков в таком случае не особо сложно. Однако учтите, что чем выше делитель таймера (а значит и обратно-пропорционально снижается точность таймера) — тем реже вызываются прерывания и меньше «кушают» наше процессорное время!
❯ Можно ли написать свою программу для Тетриса?
К сожалению, написать какую-нибудь свою игру для этой консоли в домашних условиях невозможно — таких удобных инструментов для прошивки, как у AVR ещё не было. Holtek предлагала собственный SDK, в которое входила IDE, ассемблер и симулятор отладки финальной программы. Однако дабы получить настоящее «хардварное» устройство, необходимо было заказывать у компании Holtek производство кастомизированного чипа с вашей программой на борту.
Чипсет использовал настоящую масочную Read-Only Memory, которая прожигалась один раз и навсегда на заводе. Производитель электроники отсылал скомпилированную программу Holtek, а они в свою очередь производили кастомный чип с прошитой программой. Несмотря на всю простоту ассемблера и устройства в целом, самому под него ничего написать не получится — внешних шин то у него нет. :(
❯ Заключение
Разработка вычислительной и при этом недорогой электроники в 90-х было весьма веселым занятием. Несмотря на то, что устройства были на первый взгляд достаточно примитивными, в них всё равно крылось много разных нюансов, которые ограничивали программистов во многом.
Однако embedded-разработка тех лет была весьма интересной: когда полноценные игры вмещали в ПЗУ размером пару килобайт, на ремейки Space Invaders на современных движках весом в под сотню мегабайт смотришь с некоторой улыбкой. :)
Спасибо за наводку ресурсу retroscene:
Пост Legnahar, который один из первых опубликовал предположения насчет HT1130 и комментарию =A=L=X= под тем же постом.
Автор: Богдан