Статья с сайта безумного инженера-самодельщика Криса Фентона
Встречайте ZedRipper – 16-ядерного зверюгу, работающего на частоте 83 МГц на базе процессоров Z80 – настолько же портативного, насколько и непрактичного. Это моя самая свежая на сегодня попытка собрать компьютер ради прикола, причём удовлетворив сразу несколько желаний:
- Использовать, наконец, гигантский FPGA, который валялся у меня без дела.
- Поиграть в альтернативную историю создания компьютеров, подойдя к вопросу многозадачности с позиции железа.
- Собрать компьютер, на котором я мог бы писать забавные короткие программки по пути на работу в поезде.
- Собрать платформу, на которой можно было бы проводить относительно несложные эксперименты с компьютерной архитектурой.
Гламурные фоточки
Если у вас нет времени читать простыню текста о непрактичной компьютерной архитектуре…
Так что это за зверюга?
ZedRipper – это результат попытки построить самый крутой компьютер с CP/M 2.2:
- 16 процессоров Z80, работающих на частоте 83,33 МГц.
- 64 Кб выделенной памяти под каждый Z80.
- ANSI-совместимый ускоритель терминала с 16 выходами.
- Все процессоры и устройства соединяются полностью синхронной однонаправленной кольцевой сетью, работающей на 83 МГц.
- Накопитель на 128 Мб на SD-карточке (16 х 8 Мб накопителей в CP/M).
- «Серверное» ядро, загружающееся в CP/M 2.2 и запускающее файловый сервер CP/NET (написанный на Turbo Pascal 3 на самом же компьютере!), обеспечивающий общий доступ к SD-карте.
- 15 «клиентских» ядер, запускающих CP/NOS из ROM. У каждого клиента есть доступ в общее хранилище, и каждый может запускать любые программы CP/M 2.2, не соперничая за ресурсы с другими ядрами.
Другая дорога
Это что, шахматы и Planetfall, отвлекающие меня от моего редактора Turbo Pascal?
После моих приключений с портированием игр на Kaypro у меня осталось удивительно тёплое впечатление от этой примитивной операционной системы 40-летней давности, и у меня появилась идея, которую я решил развить: что, если бы история свернула в другую сторону, и ПК пошли бы по пути развития с множеством CPU прямо сразу? Даже в 1980-х сами процессоры (а вскоре и RAM) были относительно недорогими, однако многозадачность ПК базировалась исключительно на разрезании отрезков времени, когда один большой ресурс (RAM или CPU) делили между соревнующимися программами. Железо не справлялось с этим (и было очень сложно заставить программы вести себя хорошо в таких ОС, как DOS), пока мы не перешли в эру 386-х и компьютеров с объёмом памяти более 4 Мб.
В процессе занятий моими историческими хобби с компьютерами я наткнулся на нечто очень для меня интересное: на ранних этапах развития ОС CP/M поддерживала «сетевую» версию под названием CP/NET. Её идея знакома большинству людей и сегодня – поставить в офисе одну-две «настоящие» машины с большими накопителями и принтерами, ресурсы которых делили бы между собой тонкие клиенты, терминалы с CPU и RAM. Каждый пользователь работал бы так, будто у него есть собственная машина под управлением CP/M с доступом к объёмным дискам и принтерам.
Как я упоминал, CPU и RAM (обычно у Z80 было 64 Кб DRAM) были не особенно дорогими, однако все внешние приблуды, необходимые для создания полезного компьютера (диски, принтеры, мониторы,..) добавляли итоговой стоимости. В то время добавление в компьютер нескольких CPU/RAM казалось несколько декадентским подходом для обеспечения одного пользователя несколькими CPU и RAM. Даже CP/M пошла по пути разделения отрезков времени для MP/M OS.
Я обнаружил, что ближе всех к этому подошла компания Exidy – в 1981 году они выпустили свою машину Multi-NET 80, позволявшая добавлять до 16 карточек, на каждой из которых был Z80 и RAM. Однако она была предназначена для работы до 16 отдельных пользователей, а не для работы одного пользователя, одновременно запускавшего 16 программ.
Так близко…
Перенесёмся на 40 лет вперёд – транзисторы реально подешевели. Мне по наследству после закрытия лаборатории досталось несколько чудовищных по размеру FPGA (Stratix IV 530GX), и я думал, что бы такого интересного сделать с одной из них. В какой-то момент я наткнулся на очень интересный проект Гранта Сёрла Multi-Comp, и собрать рабочую машину с CP/M и одним CPU оказалось довольно легко. Но мне нужно было больше. Я решил посмотреть, удастся ли мне создать многоядерную машину на CP/M с реальной многозадачностью – ничего хитроумного, просто метод грубой силы.
Настраиваем и запускаем софт
В этом проекте я в основном концентрировался на железе, и не написал ни одной строчки кода на ассемблере. CPU 0 грузится прямо с ROM, который я взял у Гранта, а остальные узлы грузятся с 4KB CP/NOS ROM, которые я взял у симулятора Atari.
Оба ROM ожидают соединения с последовательным терминалом по стандартному интерфейсу, а клиенты CP/NOS ожидают ещё одного последовательно порта, соединённого с сервером. На таких крупных FPGA легко проектировать собственную логику. Я разработал свою логику декодирования адресов, благодаря которой Z-Ring для каждого CPU появляется в схеме отображения адресов там, где надо.
Внутренности
Сердце ZedRipper – одна из этих огромных Stratix IV 530GX FPGA. Карта HSMC используется для дисплея, получения данных от контроллера клавиатуры и соединения с SD-картой. Для загрузки прошивки используется ethernet, поэтому сбоку корпуса есть такой порт, вместе с адаптером SD-карты и слотом для внешнего последовательного порта (пока не используется).
Клавиатура и контроллер
Клавиатура и отверстие на переднем плане, куда потом будет установлено позиционирующее устройство
У меня валялась компактная клавиатура PS/2 (от одного из моих старых проектов с ноутбуком), и я хотел подсоединить её к 2,5 В I/O моего FPGA. Я решил пойти по лёгкому пути, и добавить в связку микроконтроллер Teensy 2.0.
Контроллер на горячем клее снизу клавиатуры
Это позволило транслировать PS/2 в ASCII, а также легко разметить некоторые из дополнительных клавиш (F1-F12) на «волшебные» последовательности терминальных команд, для пущего удобства. Затем контроллер выдаёт Z80 байты по UART на 9600 бод (при помощи простого делителя напряжения, меняющего 5 В на 2,5 В для FPGA). Учитывая, что этот проект был собран из разного хлама, валявшегося у меня в мастерской, это было удобное решение, хорошо показавшее себя в работе.
Дисплей
Экран загрузки, в левом верхнем углу работает сервер, а на отдельных ядрах работают три разные пользовательские программы
Характеристики дисплея: 1280×800 10.1″, и он понимает VGA. FPGA использует простую сеть из резисторов для выдачи до 64 цветов (R2G2B2). Дисплею требуется таймер 83,33 МГц (1280×800@60Hz), поэтому для простоты вся схема работает на этой частоте.
В проекте Гранта, Multicomp, был код VHDL для простого ANSI-совместимого терминала. Я переписал его логику на Verilog, а потом разработал видеоконтроллер с поддержкой 16 независимых терминалов, соединённых через один узел Z-Ring. Дисплей 1280×800 считается дисплеем размером 160х50 символов (со шрифтом 8х16), а каждый терминал работает как «спрайт» 80х25, который можно переместить в любое место экрана (со списком приоритетов, настраивающим последовательность отрисовки терминалов). Поскольку каждый терминал работает независимо от других, у него есть собственный конечный автомат, с 2 Кб RAM для символов и 2 Кб RAM «атрибутов» (для хранения информации о цветах). Каждый символ поддерживает 4-битный цвет фона и символа. Поскольку у всех терминалов символы должны иметь одинаковые отступы, а в «ячейке» 8х16 может содержаться только один символ, все терминалы могут пользоваться одним и тем же 2 Кб ROM, содержащим шрифт. В целом логика дисплея использует около 66 Кб блочной RAM.
В целом получается очень простой менеджер окон для моих терминалов CP/M, почти полностью работающий за счёт железа. Это одна из наиболее богатых для исследования областей – пока что переставлять терминалы местами умеет только серверный CPU, но у меня есть далеко идущие планы по добавке позиционирующего устройства типа мыши, которое позволит, пользуясь только средствами железа, перетаскивать окна и менять приоритет дисплеев.
Поскольку контроллер терминала – это просто один из узлов Z-Ring (а перенаправлять этот интерфейс для любого из Z80 очень просто), среди будущих планов – возможно, добавление «полноэкранного» терминала 160х50 (возможно, как «фон»), и реального дисплея 1280x800x64 цвета при помощи быстрой внешней SRAM на плате.
Z-Ring
Как соединить вместе кучку Z80? На моей работе я прочно усвоил одну вещь: разрабатывать сети сложно. Общими целями данной сети были:
- Простая реализация.
- Простой интерфейс.
- Произвольная расширяемость.
- Адекватное быстродействие.
Как я уже упоминал, мои Z80 ожидают подключения к последовательным портам, поэтому интерфейс сделать было довольно просто – его нужно было замаскировать под последовательный порт! По сути, Z-Ring – это синхронная однонаправленная кольцевая сеть, использующая «кредиты» для управления потоком. У каждого узла есть 1-байтовый входящий буфер для каждого из остальных узлов сети. После сброса у каждого узла есть 1 «кредит» для каждого из остальных узлов сети. Схема параметризирована, поэтому легко масштабируется до сотен узлов с добавлением совсем небольшого количества логики, однако на сегодня Z-Ring поддерживает до 32 узлов (поэтому каждому узлу нужен 32-байтный буфер).
Сама «шина» состоит из бита допустимости, ID «источника», ID «цели» и 1-байтной полезной нагрузки (19 бит). Думаю, её было бы довольно просто реализовать при помощи логики TTL (если бы человек провалился в 1981 год и не нашёл себе FPGA). У каждого «узла» есть 2 конвейера для триггеров шины – 0 и 1 этапа – и при вводе сообщения оно ждёт, пока не опустеет 0-й этап, а потом вливается в 1-й. Сообщения вводятся на узле-«источнике», и путешествуют по кольцу, пока не дойдут до цели, после чего они оказываются в соответствующем буфере и обновляют флаг «готовности данных». Когда принимающий узел считывает буфер, он заново вводит оригинальное сообщение, продолжающее путешествовать по кольцу, пока снова не дойдёт до источника, возвращая «кредит». Если отправить пакет по несуществующему адресу, кредит вернётся автоматически, пройдя полный круг.
Поскольку каждая остановка на кольце состоит из двух конвейерных этапов, а обратное давление отсутствует, у каждого сообщения уходит не более 2*(количество узлов) циклов на доставку. У текущей реализации 17 узлов (16 CPU + контроллер дисплея/клавиатуры), и она работает по таймеру 12 нс, поэтому на доставку сообщения и возврат кредита уходит порядка 400 нс. Контроллер дисплея может отправлять трафик со скоростью поступления, поэтому у каждого CPU есть 2-2,5 Мб/с пропускной способности к своему терминалу (шина пропускает достаточно, чтобы обеспечить все 16 CPU), что довольно много для терминалов.
В текущей конфигурации всё работает отлично, однако можно сделать несколько очевидных улучшений:
- Углубить получающие буферы, что позволит увеличить пропускную способность узлов – на FPGA есть много свободных блоков по 1 Кб RAM, что позволит поддерживать 32 узла по 32 кредита, поэтому каждый CPU в теории может насытить шину.
- Добавить поддержку адресного режима. Добавление 16-битных (и более) адресов позволит организовать прямой доступ к памяти (DMA) (а добавить DMA к каждому узлу будет просто). У FPGA есть огромное количество дополнительного железа (несколько мегабайт статической RAM и порядка гигабайта DDR3).
- Добавить управление потоком (и буферизацию) между узлами.
Но всё это может подождать до лучших времён.
Питание!
FPGA требует на вход 14-20 В, дисплею нужны 12 В, а клавиатуре и контроллеру нужны 5 В. Удобно, что на FPGA есть регуляторы на 3,3, 5 и 12 В, к которым довольно легко подключиться, поэтому FPGA получает питание напрямую от литий-полимерной аккумуляторной батареи на 5000 мА*ч с напряжением 14,4 В, и потом раздаёт питание всем остальным устройствам. Одна из сложностей заключалась в том, что мне не хотелось разбирать ноутбук каждый раз для зарядки, но у батареи был обычный разъём питания + / -, а также «балансировочный» разъём, подсоединяемый к каждой отдельной ячейке. Моё неидеальное решение состоит в том, что кнопка включения переключает соединение с батареей между питанием FPGA и зарядным разъёмом, находящимся в углублении, закрытом сдвижной крышкой. Не очень удобно, однако можно просто сдвинуть крышку, и вытащить оттуда коннекторы, чтобы подсоединить их к зарядке, не пользуясь шестигранными ключами.
Зарядка выглядит странновато
Я не тестировал батарею тщательно, но она держит не менее 3 часов (чего с лихвой хватает для покрытия моих поездок на поезде). Скорее всего, она продержится порядка 6 часов без какой бы то ни было оптимизации потребления. Она не поддерживает использование одновременно с зарядкой, однако от батарейки ноутбук работает достаточно долго для того, чтобы это не было проблемой.
Корпус
Корпус стандартной «хакерской» конструкции – комбинация из 3 мм фанеры лазерной резки и пластика, распечатанного на 3D-принтере. Я подпружинил экранные петли, поэтому в деле он ощущается как обыкновенный, пусть и несколько медленный, ноутбук. Мне хотелось придать ему вид 1980-х, поэтому верхние углы экрана немного напоминают Cray, а под запястья сделана подставка из искусственной кожи. Край резаной лазером фанеры очень неприятен для рук, поэтому такая подставка оказалась удивительно функциональной.
Скорость
Не пробовал ни одного бенчмарка специально для CP/M (предполагаю, что они бывают, но особенно не искал). Поскольку эта машина была сделана для написания программ на Turbo Pascal, я попробовал несколько микро-тестов на скорость. Получилось 15-35 К операций с плавающей запятой в секунду (с использованием 48-битного типа Real в TP), и порядка 1 млн целочисленных операций в секунду (с 16-битным типом Integer). Неплохо для 8-битного CPU и достаточно удобной среды для программирования.
Интересным проектом на будущее может быть разработка ускорителя для операций с плавающей запятой.
Утилизация FPGA
Вся логика, как я уже говорил, довольно лёгкая, и отнимает всего около 7% ресурсов чипа (хотя 40% от общей блочной RAM и 100% от M144k RAM).
- Combinational ALUTs 31,808 / 424,960 ( 7 % )
- Memory ALUTs 0 / 212,480 ( 0 % )
- Dedicated logic registers 10,231 / 424,960 ( 2 % )
- Logic utilization 10 %
- Total registers 10231
- Total block memory bits 9,005,056 / 21,233,664 ( 42 % )
- DSP block 18-bit elements 0 / 1,024 ( 0 % )
Планы на будущее
В моих ближайших планах (то есть, железо уже лежит в мастерской, просто надо найти время на пайку):
- Покрасить всё это. Ноутбук сделан из фанеры, и его очень хочется чем-то покрыть.
- Позиционирующее устройство типа джойстик. Соединить его с клавиатурным контроллером.
- Отслеживание батареи. ADC на контроллере клавиатуры позволит достаточно легко отслеживать батарею, чтобы мне было понятно, на каком уровне заряд.
- WiFi – у меня валяется ESP32 для запуска Zimodem! Вместе с телефоном в режиме точки доступа это должно позволить мне выходить в интернет в пути. Для CP/M есть хорошие терминальные приложения, однако было бы прикольно написать что-то типа IRC-клиента или простейшего веб-браузера. Также будет удобно использовать протокол передачи файлов Kermit на современный компьютер под управлением Linux.
- Последовательный порт, доступный снаружи, для соединения с другой машиной (для него уже распечатан разъём, его нужно только припаять).
- Светодиод, сообщающий текущий статус. Для него уже есть отверстие спереди – сейчас я планирую соединить его с сигналом доступа к SD-карте.
В более долгосрочной перспективе рассчитываю на разные железячные идеи, с которыми будет прикольно поэкспериментировать:
- Как сильно можно разогнать Z80? Первый шаг – отвязать скорость процессора от пиксельного таймера, однако также будет интересно попробовать применить современные компьютерные техники к Z80 (конвейеры, переименование регистров, предсказатель переходов, и т.п.).
- Может быть интересно добавить специальные ускорители для таких вещей, как операции с плавающей запятой. На чипе есть 1024 неиспользуемых DSP-блоков, и думаю, что никто ещё не пробовал построить ускоритель для 48-битного формата Real в TP.
- Использовать существующее железо! У меня остаётся куча неиспользованной памяти, а именно:
- 512 MB DDR3 SDRAM with a 64 bit data bus
- 128 MB DDR3 SDRAM with a 16 bit data bus
- Two 4 MB QDR II+ SRAMs with 18 bit data buses
- 64 MB flash
- 2 MB SSRAM
- Улучшить видео! Первый шаг – добавить поддержку «полноэкранного» терминала 160х50, и возможность масштабировать до обычного терминала 80х25 в 2 раза. С использованием внешней SSRAM будет просто добавить режим 1280×800@6-bit.
- Расширить возможности текущего терминала. Думаю, я смогу добавить совместимость с терминалом типа ADM-3A (и добавить поддержку графики), который используется в Kaypro/84, тогда у меня будет доступ к более широкому спектру ПО (и не придётся портировать DD9).
Итоги
Пока что машина работает всего несколько дней, однако могу сказать, что мне всё очень нравится. Экран приятный и чёткий, клавиатура большая и удобная, корпус громоздкий, но весит мало (и влезает в рюкзак). Ноутбук оказался даже на удивление эргономичным для работы в поезде.
Мне кажется, я на верном пути. Возможность открыть текстовый редактор в одном окне для заметок, отлаживая код на TP в другом, чрезвычайно удобна (или возможность делать заметки, играя в Zork!). Чувствуется, что такой подход к созданию недорогих многозадачных компьютеров на базе CP/M мог существовать.
Хочется построить такой же?
Пока что у меня нет лёгкого способа доставать файлы из машины, поэтому самая полезная часть ПО (файловый сервер CP/Net, написанный на Turbo Pascal), находится в ловушке. Оставайтесь с нами, и следите за новостями (или напишите мне емейл, если совсем не терпится). В какой-то момент я, наверное, присоединюсь к XXI веку и открою учётку на github. Увы, всё упирается в то самое «свободное время».
Автор: Вячеслав Голованов