惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

F
Full Disclosure
Recorded Future
Recorded Future
T
Tenable Blog
S
Securelist
C
CERT Recently Published Vulnerability Notes
T
Threatpost
S
Schneier on Security
A
Arctic Wolf
The Hacker News
The Hacker News
C
CXSECURITY Database RSS Feed - CXSecurity.com
Know Your Adversary
Know Your Adversary
P
Privacy International News Feed
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
The Register - Security
The Register - Security
Cisco Talos Blog
Cisco Talos Blog
AWS News Blog
AWS News Blog
K
Kaspersky official blog
T
True Tiger Recordings
T
Threat Research - Cisco Blogs
V
Vulnerabilities – Threatpost
P
Palo Alto Networks Blog
T
The Exploit Database - CXSecurity.com
小众软件
小众软件
B
Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Microsoft Azure Blog
Microsoft Azure Blog
Cyberwarzone
Cyberwarzone
C
Cybersecurity and Infrastructure Security Agency CISA
T
Tor Project blog
Spread Privacy
Spread Privacy
Malwarebytes
Malwarebytes
P
Proofpoint News Feed
F
Fox-IT International blog
F
Fortinet All Blogs
P
Privacy & Cybersecurity Law Blog
G
GRAHAM CLULEY
量子位
Latest news
Latest news
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 叶小钗
Project Zero
Project Zero
T
Tailwind CSS Blog
N
Netflix TechBlog - Medium
Martin Fowler
Martin Fowler
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
I
Intezer
博客园_首页
腾讯CDC
H
Hackread – Cybersecurity News, Data Breaches, AI and More
D
Darknet – Hacking Tools, Hacker News & Cyber Security

Все публикации подряд на Хабре

256 зелёных тестов на нерабочем коде. Так выглядит «услужливый клерк» внутри нейросети Бизнес-аналитика для сети из 300 аптек: прогноз продаж и другие показатели Impact Analysis в дизайн-системе: как мы сделали CI осмысленнее, а review понятнее Топ-5 лучших нейросетей 2026 года: полный список на любой случай в SpeShu.AI Что делает сотрудников по-настоящему эффективными: процессы, знания или технологии Как за один вечер я написал сервис инвентаризации оргтехники для филиальной сети из 16 локаций Склад нанимает — и не может остановиться. Дефицит складских работников в 2026 году: причины и решения Шёл за утечкой памяти, нашёл утечку диска: SXSSFWorkbook без dispose() в Apache POI Штраф в размере 155 000 рублей получил владелец сайта по заявлению Роскомнадзора Индивидуальный план развития: от формальной процедуры к инструменту управления экспертизой команды Как понять, что вы не управляете финансами, а просто смотрите на цифры Водоросли и микропластик Масштабирование LLM: от одного чипа до ЦОДа. Глава 3. Траснформеры Бомба замедленного действия взорвалась: эпоха ИИ «бери сколько унесёшь» закончилась Стимпанк как часть жизни. История паровых двигателей и место, которое они занимали в мире в XIX-XX веках. Часть 2 288-ядерный Xeon 6+ и другие серверные CPU От OCR к смыслу: как мы научили модель понимать, кто кому отец, мать, жених и свидетель Приручаем железо: внедряем DevOps в промышленной разработке Когда Reality не хватает: добавляем Hysteria2 + Salamander в iOS-мессенджер, и как всегда грабли по дороге (ч.2) Разработчики не экстрасенсы: как мы перестали приносить туман вместо ТЗ Дайджест C++: новости, полезные материалы и “свой язык” на десерт Ещё один репозиторий моделей для Archi 10 простых шагов, чтобы создать позиционирование для продукта Загадочная поэма древнего Китая, работающая как компьютер CLOUD Act, GDPR и ваш DNS: что на самом деле может ваш провайдер Ускоряем и оптимизируем numpy, pandas, scipy и sklearn Idempotency keys: 5 граблей, которые мы поймали на проде Gamedev. Парсинг данных из Google Sheets и Excel в json без привлечения программистов Nano Banana Google AI: как использовать Нано Банана для генерации и редактирования изображений Два игрока на весь российский рынок ИИ: что показал ЦИПР-2026 Менеджер ресурсов ЯНДЕКС 360 (YANDEX 360) промокоды июнь 2026: промокод Yandex 360 скидка 40% на годовые тарифы Open-Source инструмент для автоматического перевода книг Ищу ранних тестировщиков для Android-версии agent harnesses Не используйте LLM для текста Увеличиваем продажи без слез аналитика Оптимизация запросов к PostgreSQL: 5 неочевидных настроек для продакшена 45 лет тюрьмы за DROP TABLE и переход Карпатого в Anthropic Планирование движения для ровера на ходовой Ackerman'а Революция в изучении языков Java — быстрая. Ваш код может таким не быть Как я опоздал на конкурс OpenAi с новой архитектурой нейросети Быстрые интеграции в 1С: прощайте, бесконечные переделки Как получить субсидию 300 миллионов от Минпромторга? preIPO Anthropic, OpenAI, SpaceX. Разбираемся — стоит ли участвовать? Entaxy ION + OPC UA: два способа получить данные с промышленного оборудования Память на миллион, а толку ноль: как мы спасали ИИ-агента от «тупости» РСЯ, AdSense или myTarget: что на самом деле в 2026 приносит больше денег сайту и причем тут монетизаторы Практическое построение сервисов на Go под реальный трафик PostgreSQL и аналитика: что меняется, когда хранилище становится общим Codex за 5 месяцев 2026: мой топ-5 релизов, что не зашло и где OpenAI обогнал Anthropic Как создать короткое видео с помощью нейросетей: Полный гайд по Veo 3.1, Kling 3.0 и Happy Horse 1.0 Алгоритм проверок физлиц от экс сотрудника ФНС Как ИИ портит резюме студентам Системные вызовы в сфере ИТ в 2026: стратегический взгляд для ИТ-руководителей Вайбкодинг заканчивается на localhost: как я строю SaaS для цифровизации коттеджных поселков с Codex Производственные риски в небольшом кастомном производстве. С чем я сталкивалась и как научилась это учитывать Подключаем ИИ органы чувств: bash-демон, пайка и самосознание на Raspberry Pi Я хотел повторить Growing Neural CA за вечер. Ушёл месяц Промт для генерации текста без ИИ следа — как писать уникальные тексты через нейросеть От capabilities к AppArmor: что реально остановит атакующего в контейнере CactOS Вектора интересов: как находить настоящую мотивацию и усиливать команды Цена безопасности [Перевод] Цена безопасности “Рубик” от пет-проекта до прода или ITIL 4 для строительно-торговых центров Чего ждать (и не ждать) от ремейка AC4 Black Flag Архитектурный тупик корпоративного хранения: почему смена модели не снимает ограничений и что с этим делать Атаки через подрядчиков, дефицит кадров и квест с импортозамещением: главные вызовы ИБ в 2026 году Я не оставлю детям наследства Почему порты стали «дверями» в сервер, и кто решил, что SSH будет 22 Почему зарубежные разработчики чипов возвращаются на китайские фабрики Как у меня НЕ получился торговый бот на Polymarket Проектирование архитектуры в нотации ArchiMate с использованием ИИ. Часть 2 Как превратить домашнюю файлопомойку в умную AI-галерею на основе сборки из x99+Xeon и видеокарты за 2 тыс рублей Перспективы заселения нашей галактики Кризис менеджмент в ИТ Reactive Programming не спасёт вас. Если вы не решили эти 5 проблем — у вас просто медленный монолит с Flux Как я делаю DIY-контроллер для ПК: громкость, приложения, MIDI, OBS Миграция микросервисов на Python с помощью LLM: экономим месяцы для разработчиков Программирование микросхем GAL и им подобных Почему таск-трекер не заменяет ИСУП: из чего состоит полноценный контур управления проектами Всё об информационной безопасности. Кибербезопасность. DevOps, CI/CD. Хакеры. Алексей Федулаев Как импортировать базу клиентов в amoCRM и навести порядок в контактах Как мы четыре раза переписали Outbox Google предлагает единый «водяной знак» для изображений, видео и текста, созданных ИИ Сексизм в IT: данные вместо домыслов Один фронтенд, чтоб править всеми, один фронтенд, чтоб всех найти: 1 точка входа, разные BI ИИ в тестировании: зачем мы пошли в пилот и почему начали с чата, а не с агентов Как я научила Telegram-бота наводить порядок в чате с мемами: пересылка по хештегам в соответствующую тему Как мы сделали внутреннюю CRM для управления студией – опыт Doubletapp Десятипальцевый метод — как печатать цифру " Шесть "? Партнерская программа по нейросетям: зарабатывай на ИИ, приводя клиентов в AI-сервис Как я сделал «клик по элементу → открыть в VS Code» за один вечер Эволюция Telegram‑бота на C++: от «лапши» в main() до ООП, in‑memory кэша и мутов по Фибоначчи Как я (внезапно) стал адвокатом вайб‑кодинга в корпорации Дизайн за 5 минут. Дайджест мая 2026 Только 17% всех 64-битных целых чисел можно разложить на два 32-битных 0,000000001% × ∞ = 100%. Вы осознаёте что любое событие неизбежно? «Вы либо трусы наденьте, либо крестик снимите». Как мы выиграли еще один суд против PR-агентства PRslon
Насколько плох был Intel iAPX 432 — проверяем на практике
mark_ablov · 2026-05-27 · via Все публикации подряд на Хабре

Был такой процессор в 80х - Intel iAPX 432. Он разрабатывался в качестве преемника 8080 и изначально даже имел кодовое обозначение 8800. Intel заложила в этот процессор очень много всего - абсолютно новая архитектура, совершенно не похожая на предшественников, и даже некоторые концепции ОС, реализованные прямо в кремнии - поддержка объектно-ориентированного программирования, сборщик мусора, планировщик процессов, асинхронные коммуникации, несколько уровней отказоустойчивости и многое другое.

iAPX 432

iAPX 432

Однако из-за своей сложности архитектура провалилась. Существует несколько post-mortem’ов с описанием проблем и причин провала, но если вкратце, то технологии того времени сильно ограничивали сложность физического чипа. Intel пошла на несколько компромиссов, которые сильно повлияли на производительность. Центральный процессор пришлось разбить на две микросхемы, поскольку не получилось уместить всю логику в один чип. При этом даже этого было недостаточно, чтобы включить все нужные фичи, даже такие полезные как регистровый файл.

Да-да, у iAPX 432 был только один косвенно доступный регистр общего назначения (16-битный top-of-stack), а все остальные обращения к переменным шли через память. Причём данная система от Intel позиционировалась как основанная на полномочиях (возможно, термин capability-based звучит более знакомо), а значит доступ к данным был куда более сложным, чем просто считать или записать значение по конкретному адресу в памяти. К этому я ещё вернусь, но данное решение усугубило проблемы архитектуры.

Было ещё несколько спорных моментов, часть из которых поменяли в следующей ревизии. Изменения были весьма глобальными и частично исправили проблемы iAPX 432, но поезд уже ушёл и рынок похоронил инновационное детище Intel.

К счастью, у компании был план Б, и пока лучшие умы концентрировались на разработке прорывной системы, другая команда работала над временным решением - 8086, который должен был закрыть сиюминутные потребности общественности. В итоге, “временная” архитектура x86 стала доминировать в течение нескольких десятилетий, а iAPX 432 остался в памяти только у компьютерных энтузиастов. Да, так бывает.

Мне интересно ковыряться со старыми и странными процессорами, поэтому не мог пройти мимо возможности запустить что-нибудь на такой диковинке. Дополнительный интерес вызывало то, что, насколько мне известно, за последние пару десятков лет, никто не прикасался к работающей 432 системе.

Hardware

Процессор (он же GDP, general data processor) мне достался в комплекте платы iSBC 432/100. Это single board computer, который имел Multibus интерфейс, для того, чтобы его можно было использовать с Intel Intellec MDS. Но, само собой, мне хотелось иметь куда больший контроль над сигналами процессора и более дружелюбный интерфейс взаимодействия. Поэтому опять решил спроектировать простенькую плату с FPGA и SRAM на борту, которые покрывали бы все нужды процессора.

Спроектированная платка для запуска iAPX 432

Спроектированная платка для запуска iAPX 432

Кроме питания, необходимо было согласовать уровни сигналов (в то время многие микросхемы работали на 5v, а FPGA на 3v3). И, в принципе, это всё - плата весьма проста и отрассировалась на 2х слоях.

Из нюансов бы отметил 2 момента: я поставил TPS63002, чтобы сконвертировать плавающее напряжение с USB-коннектора в конкретные 5v, но он прожил не очень долго. То ли не рассчитан на такое применение, то ли у меня где-то ошибка.

Вторая особенность платы заключается в использовании одного коннектора для прошивки SPI флешки с FPGA-битстримом и для UART’a с хостом. Обычно я ставлю UART-USB мост, и кроме питания, USB ещё обеспечивает канал связи. Но в данном случае я перестраховался - мой ПК не может выдать достойную силу тока через USB 2.0, а по спецификации iAPX 432 может быть весьма прожорливым, и из-за этого USB кабель подключен к блоку питания. В то же время не хотелось иметь пучок проводов, и поэтому объединил 2 функции в одном коннекторе.

Чтобы вернуть ft232h в режим UART после того, как он использовался для прошивки флешки через iceprog, достаточно перезапустить модуль ядра:

sudo modprobe -r ftdi_sio
sudo modprobe ftdi_sio

Gateware

В качестве FPGA я взял Lattice iCE40HX. В первую очередь из-за наличия открытого стека для синтеза bitstream’a. Конкретную микросхему выбирал в паяемом корпусе и с достаточным количеством ножек.

Для памяти выбрал синхронную параллельную SRAM, работающую на 250МГц (время доступа было заявлено 2.6нс). Здесь я не смог достичь максимальной частоты работы (хотя в другом проекте та же связка работала на 250МГц), но 125МГц оказалось вполне достаточно для того, чтобы отвечать процессору за 1 такт (и не вводить дополнительные такты ожидания ответа от памяти), так что я не стал тратить время на поиск нужных таймингов для достижения более высокой частоты.

Отлаживаем шину

Отлаживаем шину

FPGA в моём дизайне выполняла роль контроллера памяти (ведомый на шине), генератора тактовых сигналов для iAPX 432 (их нужно 3) и взаимодействовала с управляющим софтом, запущенным на ПК. Для отладки мне хотелось иметь лог обращений к памяти со стороны GDP, чтобы исследовать логику его работы.

Если говорить о Verilog’е, то единственная проблема возникала с попытками заставить работать SRAM на 250МГц. Yosys (инструмент для синтеза) постоянно вставлял SB_DFFE элементы (D-триггеры с дополнительным входом Clock Enable), которые абсолютно не вписывались во временной бюджет (а для 250МГц он не сильно большой). В конце концов я спроектировал аккуратный модуль, который успешно синтезировался и даже работал (на более низких частотах), но увы не на 250МГц в существующей топологии.

Временная диаграма для олерации чтения

Временная диаграма для олерации чтения

Шина имеет весьма несложный интерфейс. Можно разве что упомянуть то, что почти в любой момент может прийти сигнал того, что кто-то инициировал посылку IPC сообщения (inter-processor communication).

Сам пакет запроса от ведущего устройства (GDP) содержит 32 бита - 24 бита адреса и 8 бит описания того, что хочет получить процессор.

Спецификация запроса к контроллеру памяти на шине

Спецификация запроса к контроллеру памяти на шине

Самое очевидное поле - тип операции. Хотим ли мы записать или прочитать значение из памяти. С длиной тоже всё ясно. Модификаторы несут больше информационную роль, они никак не влияют на поведение моего контроллера памяти. Access бит чуть более интересный - 432 работает с двумя адресными пространствами. Обычная память устройства и внешние регистры для межпроцессорного взаимодействия. К примеру, там может храниться идентификатор устройства. Так как у нас в системе нет Interface Processor’a (еще один процессор из семейства 432, который является мостом между 432 и обычной средой), то по большей части будет использоваться только пространство с обычной памятью.

Последний флаг (RMW) тоже весьма занятный. Он обеспечивает транзакционность на уровне шины. Расшифровывается как Read-Modify-Write. Чтение с установленным RMW флагом блокирует память по этому адресу - пока не придёт команда на запись (или не истечёт таймаут), все остальные операции чтения по этому адресу будут висеть без ответа. В моей упрощённой системе с одним GDP и пассивным ведомым контроллером памяти каких-то конкурирующих запросов к памяти не планируется, поэтому также можем не заводить логику под поддержку этой фичи.

Подготовка процессора к старту

Ранее я не упоминал, но невозможно построить функциональную систему только на процессорах семейства 432. Из-за своей объектно-ориентированной природы, iAPX 432 ожидает, что кто-то уже подготовит множество структур в памяти. Всегда должен быть какой-то attached processor (а-ля 8080 или даже 8086), который проведёт инициализацию памяти и даст сигнал о том, что можно стартовать основные блоки распределённой системы. Прежде чем начать выполнять пользовательский код, 432 GDP совершает множество телодвижений по чтению системных объектов - таблицы размещения сегментов, информация о процессоре (регистры в том самом межпроцессорном пространстве и структуры в обычной памяти), текущем процессе, сегментах кода и данных, и т.д.

Поэтому нужно построить снимок памяти и загрузить его в SRAM до того, как стартовать процессор.

После подачи сигнала INIT/, GDP начинает спамить запросами на чтение межпроцессорного регистра 0x02. Этот регистр содержит информацию о состоянии IPC - есть ли какое-то внешнее сообщение, которое нужно обработать. Если пришло IPC, то процессор начинает полноценный процесс пробуждения. В теории, память может использоваться несколькими устройствами, поэтому нельзя полагаться на заранее заданные абсолютные адреса для чтения разнообразных системных структур. Только один адрес зашит в микрокоде - адрес глобального объектного каталога. И процессор использует свой идентификатор в системе (получая его через запрос регистра 0x00) в качестве индекса в этой глобальной таблице для чтения объекта типа Processor.

И уже из него начинает получать адреса объектов, специфичных для конкретного процессора. Прежде чем исполнить первую пользовательскую инструкцию кода, GDP совершает около 150 операций с памятью. И нам нужно уметь корректно отвечать на эти запросы.

Пример лога запросов на шине со стороны GDP
[+] Connected to SBC
[+] SBC is online
[~] Building image...
[+] ROM image has been written to SBC, size = 1110 bytes
[+] GDP has been started
[~] Read access log after 2s of execution.
[+] Access log (skipped 0 entries):
  [000] GDP initialization
  [001] spec: <RD 2b, 'Other/interconnect register'> addr: 0x0002
  [002] spec: <RD 2b, 'Other/interconnect register'> addr: 0x0000
  [003] spec: <RD 4b, 'Memory/other'> addr: objectTableDirectory/objectTableProcessor (0x0018)
  [004] spec: <RD 2b, 'Memory/other', RMW> addr: objectTableDirectory/objectTableProcessor (0x0018)
  [005] spec: <WR 2b, 'Memory/other', RMW> addr: objectTableDirectory/objectTableProcessor (0x0018) <58d7>
  [006] spec: <RD 2b, 'Memory/other'> addr: objectTableDirectory+0x18 (0x0020)
  [007] spec: <RD 2b, 'Memory/other'> addr: objectTableDirectory+0x14 (0x001c)
  [008] spec: <RD 10b, 'Memory/other'> addr: objectTableProcessor/processorAccess (0x0068)
  [009] spec: <RD 2b, 'Memory/other', RMW> addr: objectTableProcessor/processorAccess (0x0068)
  [010] spec: <WR 2b, 'Memory/other', RMW> addr: objectTableProcessor/processorAccess (0x0068) <78df>
  [011] spec: <RD 4b, 'Memory/other'> addr: processorAccess+0x10 (0x0088)
  [012] spec: <RD 4b, 'Memory/other'> addr: objectTableDirectory/objectTableDirectory (0x0038)
  [013] spec: <RD 2b, 'Memory/other', RMW> addr: objectTableDirectory/objectTableDirectory (0x0038)
  [014] spec: <WR 2b, 'Memory/other', RMW> addr: objectTableDirectory/objectTableDirectory (0x0038) <08d7>
  [015] spec: <RD 2b, 'Memory/other'> addr: objectTableDirectory+0x38 (0x0040)
  [016] spec: <RD 2b, 'Memory/other'> addr: objectTableDirectory+0x34 (0x003c)
  [017] spec: <RD 10b, 'Memory/other'> addr: objectTableDirectory/objectTableDirectory (0x0038)
  [018] spec: <RD 4b, 'Memory/other'> addr: processorAccess+0x00 (0x0078)
  [019] spec: <RD 4b, 'Memory/other'> addr: objectTableDirectory/objectTableMain (0x0048)
  [020] spec: <RD 2b, 'Memory/other', RMW> addr: objectTableDirectory/objectTableMain (0x0048)
  [021] spec: <WR 2b, 'Memory/other', RMW> addr: objectTableDirectory/objectTableMain (0x0048) <d8d7>
  [022] spec: <RD 2b, 'Memory/other'> addr: objectTableDirectory+0x48 (0x0050)
  [023] spec: <RD 2b, 'Memory/other'> addr: objectTableDirectory+0x44 (0x004c)
  [024] spec: <RD 10b, 'Memory/other'> addr: objectTableMain/processorData (0x00e8)
  [025] spec: <RD 2b, 'Memory/other', RMW> addr: objectTableMain/processorData (0x00e8)
  [026] spec: <WR 2b, 'Memory/other', RMW> addr: objectTableMain/processorData (0x00e8) <e8d7>
  [027] spec: <RD 2b, 'Memory/other', RMW> addr: processorData+0x00 (0x01e8)
  [028] spec: <WR 2b, 'Memory/other', RMW> addr: processorData+0x00 (0x01e8) <0005>
  [029] spec: <RD 4b, 'Memory/other'> addr: processorAccess+0x00 (0x0078)
  [030] spec: <RD 10b, 'Memory/other'> addr: objectTableMain/processorData (0x00e8)
  [031] spec: <WR 2b, 'Memory/other'> addr: processorData+0x02 (0x01ea) <0102>
  [032] spec: <RD 4b, 'Memory/other'> addr: processorAccess+0x08 (0x0080)
  [033] spec: <RD 10b, 'Memory/other'> addr: objectTableMain/processorLocalComms (0x00f8)
  [034] spec: <RD 2b, 'Memory/other', RMW> addr: objectTableMain/processorLocalComms (0x00f8)
  [035] spec: <WR 2b, 'Memory/other', RMW> addr: objectTableMain/processorLocalComms (0x00f8) <78d7>
  [036] spec: <RD 2b, 'Memory/other', RMW> addr: processorLocalComms+0x00 (0x0278)
  [037] spec: <WR 2b, 'Memory/other', RMW> addr: processorLocalComms+0x00 (0x0278) <0005>
  [038] spec: <RD 4b, 'Memory/other'> addr: processorLocalComms+0x02 (0x027a)
  [039] spec: <WR 2b, 'Memory/other'> addr: processorLocalComms+0x04 (0x027c) <0000>
  [040] spec: <RD 2b, 'Memory/other', RMW> addr: processorLocalComms+0x00 (0x0278)
  [041] spec: <WR 2b, 'Memory/other', RMW> addr: processorLocalComms+0x00 (0x0278) <0000>
  [042] spec: <RD 4b, 'Memory/other'> addr: processorAccess+0x00 (0x0078)
  [043] spec: <RD 10b, 'Memory/other'> addr: objectTableMain/processorData (0x00e8)
  [044] spec: <WR 2b, 'Memory/other'> addr: processorData+0x02 (0x01ea) <0102>
  [045] spec: <RD 8b, 'Memory/other'> addr: processorAccess+0x18 (0x0090)
  [046] spec: <RD 10b, 'Memory/other'> addr: objectTableMain/delayCarrierAccess (0x0138)
  [047] spec: <RD 2b, 'Memory/other', RMW> addr: objectTableMain/delayCarrierAccess (0x0138)
  [048] spec: <WR 2b, 'Memory/other', RMW> addr: objectTableMain/delayCarrierAccess (0x0138) <bedf>
  [049] spec: <WR 1b, 'Memory/other'> addr: objectTableMain+0x49 (0x0121) <0001>
  [050] spec: <WR 4b, 'Memory/other'> addr: processorAccess+0x24 (0x009c) <004f 004f>
  [051] spec: <RD 10b, 'Memory/other'> addr: objectTableMain/delayPortAccess (0x0118)
  [052] spec: <RD 2b, 'Memory/other', RMW> addr: objectTableMain/delayPortAccess (0x0118)
  [053] spec: <WR 2b, 'Memory/other', RMW> addr: objectTableMain/delayPortAccess (0x0118) <9adf>
  [054] spec: <RD 4b, 'Memory/other'> addr: delayPortAccess+0x00 (0x029a)
  [055] spec: <RD 10b, 'Memory/other'> addr: objectTableMain/delayPortData (0x0108)
  [056] spec: <RD 2b, 'Memory/other', RMW> addr: objectTableMain/delayPortData (0x0108)
  [057] spec: <WR 2b, 'Memory/other', RMW> addr: objectTableMain/delayPortData (0x0108) <82d7>
  [058] spec: <RD 2b, 'Memory/other', RMW> addr: delayPortData+0x00 (0x0282)
  [059] spec: <WR 2b, 'Memory/other', RMW> addr: delayPortData+0x00 (0x0282) <0005>
  [060] spec: <RD 6b, 'Memory/other'> addr: delayPortData+0x06 (0x0288)
  [061] spec: <RD 4b, 'Memory/other'> addr: delayPortData+0x00 (0x0282)
  [062] spec: <RD 2b, 'Memory/other', RMW> addr: delayPortData+0x00 (0x0282)
  [063] spec: <WR 2b, 'Memory/other', RMW> addr: delayPortData+0x00 (0x0282) <0000>
  [064] spec: <RD 4b, 'Memory/other'> addr: processorAccess+0x14 (0x008c)
  [065] spec: <WR 1b, 'Memory/other'> addr: objectTableMain+0x89 (0x0161) <0001>
  [066] spec: <WR 4b, 'Memory/other'> addr: processorAccess+0x28 (0x00a0) <008f 004f>
  [067] spec: <RD 10b, 'Memory/other'> addr: objectTableMain/normalCarrierAccess (0x0158)
  [068] spec: <RD 2b, 'Memory/other', RMW> addr: objectTableMain/normalCarrierAccess (0x0158)
  [069] spec: <WR 2b, 'Memory/other', RMW> addr: objectTableMain/normalCarrierAccess (0x0158) <f2df>
  [070] spec: <RD 4b, 'Memory/other'> addr: normalCarrierAccess+0x00 (0x02f2)
  [071] spec: <RD 10b, 'Memory/other'> addr: objectTableMain/normalCarrierData (0x0148)
  [072] spec: <RD 2b, 'Memory/other', RMW> addr: objectTableMain/normalCarrierData (0x0148)
  [073] spec: <WR 2b, 'Memory/other', RMW> addr: objectTableMain/normalCarrierData (0x0148) <e2d7>
  [074] spec: <RD 2b, 'Memory/other'> addr: normalCarrierData+0x02 (0x02e4)
  [075] spec: <WR 2b, 'Memory/other'> addr: normalCarrierData+0x02 (0x02e4) <0008>
  [076] spec: <RD 4b, 'Memory/other'> addr: normalCarrierAccess+0x1c (0x030e)
  [077] spec: <WR 1b, 'Memory/other'> addr: objectTableMain+0xa9 (0x0181) <0001>
  [078] spec: <WR 4b, 'Memory/other'> addr: processorAccess+0x04 (0x007c) <00af 004f>
  [079] spec: <RD 4b, 'Memory/other'> addr: processorAccess+0x00 (0x0078)
  [080] spec: <RD 10b, 'Memory/other'> addr: objectTableMain/processorData (0x00e8)
  [081] spec: <WR 2b, 'Memory/other'> addr: processorData+0x02 (0x01ea) <0103>
  [082] spec: <WR 1b, 'Memory/other'> addr: objectTableMain+0xa9 (0x0181) <0001>
  [083] spec: <WR 4b, 'Memory/other'> addr: processorAccess+0x28 (0x00a0) <00af 004f>
  [084] spec: <RD 10b, 'Memory/other'> addr: objectTableMain/processCarrierAccess (0x0178)
  [085] spec: <RD 2b, 'Memory/other', RMW> addr: objectTableMain/processCarrierAccess (0x0178)
  [086] spec: <WR 2b, 'Memory/other', RMW> addr: objectTableMain/processCarrierAccess (0x0178) <26df>
  [087] spec: <RD 4b, 'Memory/other'> addr: processCarrierAccess+0x00 (0x0326)
  [088] spec: <RD 10b, 'Memory/other'> addr: objectTableMain/processCarrierData (0x0168)
  [089] spec: <RD 2b, 'Memory/other', RMW> addr: objectTableMain/processCarrierData (0x0168)
  [090] spec: <WR 2b, 'Memory/other', RMW> addr: objectTableMain/processCarrierData (0x0168) <16d7>
  [091] spec: <RD 2b, 'Memory/other'> addr: processCarrierData+0x04 (0x031a)
  [092] spec: <RD 2b, 'Memory/other', RMW> addr: processCarrierData+0x00 (0x0316)
  [093] spec: <WR 2b, 'Memory/other', RMW> addr: processCarrierData+0x00 (0x0316) <0005>
  [094] spec: <RD 4b, 'Memory/other'> addr: processCarrierAccess+0x20 (0x0346)
  [095] spec: <RD 10b, 'Memory/other'> addr: objectTableMain/processAccess (0x0198)
  [096] spec: <RD 2b, 'Memory/other', RMW> addr: objectTableMain/processAccess (0x0198)
  [097] spec: <WR 2b, 'Memory/other', RMW> addr: objectTableMain/processAccess (0x0198) <dadf>
  [098] spec: <RD 4b, 'Memory/other'> addr: processAccess+0x00 (0x03da)
  [099] spec: <RD 10b, 'Memory/other'> addr: objectTableMain/processData (0x0188)
  [100] spec: <RD 2b, 'Memory/other', RMW> addr: objectTableMain/processData (0x0188)
  [101] spec: <WR 2b, 'Memory/other', RMW> addr: objectTableMain/processData (0x0188) <4ad7>
  [102] spec: <RD 2b, 'Memory/other', RMW> addr: processData+0x00 (0x034a)
  [103] spec: <WR 2b, 'Memory/other', RMW> addr: processData+0x00 (0x034a) <0005>
  [104] spec: <RD 6b, 'Memory/other'> addr: processData+0x20 (0x036a)
  [105] spec: <RD 4b, 'Memory/other'> addr: processAccess+0x14 (0x03ee)
  [106] spec: <WR 4b, 'Memory/other'> addr: processCarrierAccess+0x0c (0x0332) <0000 0000>
  [107] spec: <RD 4b, 'Memory/other'> addr: processAccess+0x04 (0x03de)
  [108] spec: <RD 10b, 'Memory/other'> addr: objectTableMain/processContext0Access (0x01a8)
  [109] spec: <RD 2b, 'Memory/other', RMW> addr: objectTableMain/processContext0Access (0x01a8)
  [110] spec: <WR 2b, 'Memory/other', RMW> addr: objectTableMain/processContext0Access (0x01a8) <0adf>
  [111] spec: <RD 4b, 'Memory/other'> addr: processContext0Access+0x00 (0x040a)
  [112] spec: <RD 10b, 'Memory/other'> addr: objectTableMain/processContext0Data (0x01b8)
  [113] spec: <RD 2b, 'Memory/other', RMW> addr: objectTableMain/processContext0Data (0x01b8)
  [114] spec: <WR 2b, 'Memory/other', RMW> addr: objectTableMain/processContext0Data (0x01b8) <32d7>
  [115] spec: <RD 4b, 'Memory/other'> addr: processContext0Access+0x14 (0x041e)
  [116] spec: <WR 2b, 'Memory/other'> addr: processData+0x32 (0x037c) <ffff>
  [117] spec: <WR 4b, 'Memory/other'> addr: processContext0Access+0x14 (0x041e) <0000 0000>
  [118] spec: <RD 4b, 'Memory/other'> addr: processContext0Access+0x18 (0x0422)
  [119] spec: <WR 2b, 'Memory/other'> addr: processData+0x34 (0x037e) <ffff>
  [120] spec: <WR 4b, 'Memory/other'> addr: processContext0Access+0x18 (0x0422) <0000 0000>
  [121] spec: <RD 4b, 'Memory/other'> addr: processContext0Access+0x1c (0x0426)
  [122] spec: <WR 2b, 'Memory/other'> addr: processData+0x36 (0x0380) <ffff>
  [123] spec: <WR 4b, 'Memory/other'> addr: processContext0Access+0x1c (0x0426) <0000 0000>
  [124] spec: <RD 4b, 'Memory/other'> addr: processContext0Access+0x24 (0x042e)
  [125] spec: <RD 8b, 'Memory/context'> addr: processContext0Data+0x00 (0x0432)
  [126] spec: <RD 4b, 'Memory/other'> addr: processContext0Access+0x20 (0x042a)
  [127] spec: <RD 10b, 'Memory/other'> addr: objectTableMain/processContext0Domain (0x01c8)
  [128] spec: <RD 2b, 'Memory/other', RMW> addr: objectTableMain/processContext0Domain (0x01c8)
  [129] spec: <WR 2b, 'Memory/other', RMW> addr: objectTableMain/processContext0Domain (0x01c8) <409f>
  [130] spec: <RD 4b, 'Memory/other'> addr: processContext0Domain+0x00 (0x0440)
  [131] spec: <RD 10b, 'Memory/other'> addr: objectTableMain/processContext0Instruction0 (0x01d8)
  [132] spec: <RD 2b, 'Memory/other', RMW> addr: objectTableMain/processContext0Instruction0 (0x01d8)
  [133] spec: <WR 2b, 'Memory/other', RMW> addr: objectTableMain/processContext0Instruction0 (0x01d8) <4497>
  [134] spec: <RD 4b, 'Memory/other'> addr: processorAccess+0x00 (0x0078)
  [135] spec: <RD 10b, 'Memory/other'> addr: objectTableMain/processorData (0x00e8)
  [136] spec: <WR 2b, 'Memory/other'> addr: processorData+0x02 (0x01ea) <0104>
  [137] spec: <RD 4b, 'Memory/instruction'> addr: processContext0Instruction0+0x0e (0x0452)
  [138] spec: <RD 4b, 'Memory/other'> addr: processContext0Access+0x08 (0x0412)
  [139] spec: <RD 2b, 'Memory/other'> addr: processAccess+0x0c (0x03e6)
  [140] spec: <RD 4b, 'Memory/other'> addr: objectTableDirectory:Header (0x0008)
  [141] spec: <WR 10b, 'Memory/other'> addr: processData+0x7c (0x03c6) <0000 0000 0000 0000 7fff>
  [142] spec: <WR 10b, 'Memory/other'> addr: processData+0x86 (0x03d0) <0004 010f 010f 0000 7fff>
  [143] spec: <WR 8b, 'Memory/other'> addr: processData+0x74 (0x03be) <00cd 7417 000c 0000>
  [144] spec: <WR 8b, 'Memory/other'> addr: processData+0x68 (0x03b2) <0000 0076 0070 0000>
  [145] spec: <WR 4b, 'Memory/other'> addr: processData+0x70 (0x03ba) <0000 0001>
  [146] spec: <RD 4b, 'Memory/other'> addr: processAccess+0x10 (0x03ea)
  [147] spec: <RD 4b, 'Memory/other'> addr: processorAccess+0x00 (0x0078)
  [148] spec: <RD 10b, 'Memory/other'> addr: objectTableMain/processorData (0x00e8)
  [149] spec: <WR 10b, 'Memory/other'> addr: processorData+0x54 (0x023c) <0000 0001 0001 0000 7fff>
  [150] spec: <WR 10b, 'Memory/other'> addr: processorData+0x5e (0x0246) <0000 0000 0000 0000 7fff>
  [151] spec: <WR 8b, 'Memory/other'> addr: processorData+0x4c (0x0234) <00cd 7a00 000c 0000>
  [152] spec: <WR 8b, 'Memory/other'> addr: processorData+0x40 (0x0228) <0000 0070 0070 0000>
  [153] spec: <WR 4b, 'Memory/other'> addr: processorData+0x48 (0x0230) <0000 0001>
  [154] spec: <RD 4b, 'Memory/other'> addr: processorAccess+0x14 (0x008c)
  [155] spec: <RD 10b, 'Memory/other'> addr: objectTableMain/normalCarrierAccess (0x0158)
  [156] spec: <RD 4b, 'Memory/other'> addr: normalCarrierAccess+0x00 (0x02f2)
  [157] spec: <RD 10b, 'Memory/other'> addr: objectTableMain/normalCarrierData (0x0148)
  [158] spec: <RD 2b, 'Memory/other'> addr: normalCarrierData+0x02 (0x02e4)
  [159] spec: <WR 2b, 'Memory/other'> addr: normalCarrierData+0x02 (0x02e4) <000c>
  [160] spec: <RD 4b, 'Memory/other'> addr: processorAccess+0x00 (0x0078)
  [161] spec: <RD 10b, 'Memory/other'> addr: objectTableMain/processorData (0x00e8)
  [162] spec: <WR 2b, 'Memory/other'> addr: processorData+0x02 (0x01ea) <0105>
  [163] spec: <WR 8b, 'Memory/context'> addr: processContext0Data+0x00 (0x0432) <0000 0000 0000 0070>
  [164] spec: <RD 8b, 'Memory/other'> addr: processData+0x22 (0x036c)
  [165] spec: <WR 6b, 'Memory/other'> addr: processData+0x24 (0x036e) <0043 0000 0000>
  [166] spec: <RD 2b, 'Memory/other', RMW> addr: processData+0x00 (0x034a)
  [167] spec: <WR 2b, 'Memory/other', RMW> addr: processData+0x00 (0x034a) <0000>
  [168] spec: <RD 4b, 'Memory/other'> addr: processorAccess+0x00 (0x0078)
  [169] spec: <RD 10b, 'Memory/other'> addr: objectTableMain/processorData (0x00e8)
  [170] spec: <WR 2b, 'Memory/other'> addr: processorData+0x02 (0x01ea) <0105>
  [171] spec: <RD 4b, 'Memory/other'> addr: processorAccess+0x04 (0x007c)
  [172] spec: <RD 10b, 'Memory/other'> addr: objectTableMain/processCarrierAccess (0x0178)
  [173] spec: <RD 4b, 'Memory/other'> addr: processCarrierAccess+0x00 (0x0326)
  [174] spec: <RD 10b, 'Memory/other'> addr: objectTableMain/processCarrierData (0x0168)
  [175] spec: <RD 2b, 'Memory/other', RMW> addr: processCarrierData+0x00 (0x0316)
  [176] spec: <WR 2b, 'Memory/other', RMW> addr: processCarrierData+0x00 (0x0316) <0001 0000 0000 0000 0000>
  [177] spec: <RD 4b, 'Memory/other'> addr: processorAccess+0x00 (0x0078)
  [178] spec: <RD 10b, 'Memory/other'> addr: objectTableMain/processorData (0x00e8)
  [179] spec: <WR 2b, 'Memory/other'> addr: processorData+0x02 (0x01ea) <0135>
  [180] spec: <RD 4b, 'Memory/other'> addr: processorAccess+0x4c (0x00c4)
  [181] spec: <WR 4b, 'Memory/other'> addr: processorAccess+0x14 (0x008c) <0000 0000>
  [182] spec: <RD 4b, 'Memory/other'> addr: processorAccess+0x00 (0x0078)
  [183] spec: <RD 10b, 'Memory/other'> addr: objectTableMain/processorData (0x00e8)
  [184] spec: <WR 2b, 'Memory/other'> addr: processorData+0x02 (0x01ea) <0132>
  [185] spec: <RD 8b, 'Memory/other'> addr: processorAccess+0x18 (0x0090)
  [186] spec: <RD 10b, 'Memory/other'> addr: objectTableMain/delayCarrierAccess (0x0138)
  [187] spec: <WR 1b, 'Memory/other'> addr: objectTableMain+0x49 (0x0121) <0001>
  [188] spec: <WR 4b, 'Memory/other'> addr: processorAccess+0x24 (0x009c) <004f 004f>
  [189] spec: <RD 10b, 'Memory/other'> addr: objectTableMain/delayPortAccess (0x0118)
  [190] spec: <RD 4b, 'Memory/other'> addr: delayPortAccess+0x00 (0x029a)
  [191] spec: <RD 10b, 'Memory/other'> addr: objectTableMain/delayPortData (0x0108)
  [192] spec: <RD 2b, 'Memory/other', RMW> addr: delayPortData+0x00 (0x0282)
  [193] spec: <WR 2b, 'Memory/other', RMW> addr: delayPortData+0x00 (0x0282) <0005>
  [194] spec: <RD 6b, 'Memory/other'> addr: delayPortData+0x06 (0x0288)
  [195] spec: <RD 4b, 'Memory/other'> addr: delayPortData+0x00 (0x0282)
  [196] spec: <RD 2b, 'Memory/other', RMW> addr: delayPortData+0x00 (0x0282)
  [197] spec: <WR 2b, 'Memory/other', RMW> addr: delayPortData+0x00 (0x0282) <0000>
  [198] spec: <RD 4b, 'Memory/other'> addr: processorAccess+0x14 (0x008c)
  [199] spec: <WR 4b, 'Memory/other'> addr: processorAccess+0x28 (0x00a0) <0000 0000>
  [200] Fatal signal is raised by GDP

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

Граф объектов, нужных для запуска GDP

Граф объектов, нужных для запуска GDP

Ну или в программном виде:

  const processorObjectTable = new ObjectTable('objectTableProcessor');
  // empty, would not be used
  const tempDirObjectTable = new ObjectTable('objectTableTemp');
  const mainObjectTable = new ObjectTable('objectTableMain');
  const directoryObjectTable = new ObjectTable('objectTableDirectory');

  const objectDirectory = new ObjectTableDirectory(directoryObjectTable);
  objectDirectory.addObjectTable(processorObjectTable);
  objectDirectory.addObjectTable(tempDirObjectTable);
  objectDirectory.addObjectTable(directoryObjectTable);
  objectDirectory.addObjectTable(mainObjectTable);

  // processors object table contains only processor access segments
  processorObjectTable.addObject(new ProcessorAccessSegment('processorAccess', { directoryObjectTable }));

  // interconnect segment for UART output
  mainObjectTable.addInterconnectSegment('uartInterconnect', 0x1000, 0x10);

  // here is all objects, except processor access segments
  mainObjectTable.addObject(new ProcessorDataSegment('processorData'));
  mainObjectTable.addObject(new LocalCommunicationSegment('processorLocalComms'));
  // delay port
  mainObjectTable.addObject(new PortDataSegment('delayPortData', { messageQueueSize: 1, portType: PORT_TYPE.DELAY }));
  mainObjectTable.addObject(new PortAccessSegment('delayPortAccess', { directoryObjectTable, messageQueueSize: 1 }));
  mainObjectTable.addObject(new CarrierDataSegment('delayCarrierData', { carrierType: CARRIER_TYPE.PROCESSOR }));
  mainObjectTable.addObject(new CarrierAccessSegment('delayCarrierAccess', { directoryObjectTable }));
  // actual process objects
  mainObjectTable.addObject(new CarrierDataSegment('normalCarrierData', { carrierType: CARRIER_TYPE.PROCESSOR, hasMessage: true }));
  mainObjectTable.addObject(new CarrierAccessSegment('normalCarrierAccess', { directoryObjectTable, messageRef: 'processCarrierAccess' }));
  mainObjectTable.addObject(new CarrierDataSegment('processCarrierData', { carrierType: CARRIER_TYPE.PROCESSOR, hasMessage: true }));
  mainObjectTable.addObject(new CarrierAccessSegment('processCarrierAccess', { directoryObjectTable, carriedObjectRef: 'processAccess' }));
  mainObjectTable.addObject(new ProcessDataSegment('processData'));
  mainObjectTable.addObject(new ProcessAccessSegment('processAccess', { directoryObjectTable }));
  mainObjectTable.addObject(new ContextAccessSegment('processContext0Access', { directoryObjectTable, objectsRefs: ['uartInterconnect', 'processContext0Vars'] }));
  mainObjectTable.addObject(new ContextDataSegment('processContext0Data', { sp: 0 })); // stack grows upward, push increments SP, pop - decrements
  mainObjectTable.addObject(new GenericDataSegment('processContext0Stack', { size: stack.size, data: stack.data, type: SEGMENT_TYPE.OPERAND_STACK_DATA }));
  mainObjectTable.addObject(new GenericDataSegment('processContext0Vars', { data: varsData, type: SEGMENT_TYPE.GENERIC_DATA }));
  mainObjectTable.addObject(new DomainSegment('processContext0Domain', { directoryObjectTable, instructionsRefs: ['processContext0Instruction0'] }));
  mainObjectTable.addObject(new InstructionSegment('processContext0Instruction0', { directoryObjectTable, instructions: bytecode, contextIdx: 0 }));

Не буду останавливаться на каждом запросе к памяти, а пройдусь концептуально - опишу интересные моменты и особенности iAPX 432.

Во-первых, нужно упомянуть, как работает трансляция указателей на объекты в физические адреса. Каждый указатель состоит из двух частей - индекс таблицы объектов и индекс самого объекта в таблице. То есть чтобы вычислить физический адрес, процессору нужно сначала найти адрес таблицы (через чтение центрального каталога, который и содержит адреса конкретных таблиц), а затем уже из этой таблицы прочитать дескриптор, в котором записан физический адрес.

уот так уот

уот так уот

Зачем такие сложности? А потому что нужно было реализовать несколько фич:

  1. Каждый указатель кроме адреса содержит права, которыми обладает этот указатель. Можно ли его использовать для чтения/записи/… Причём возможный набор прав зависит от типа объекта. Скажем, если у нас есть указатель на объект типа Processor, то можно разрешить его использование для конкретных высокоуровневых операций, таких как отправить сообщение процессору, или запросить его счётчик тактов (в памяти этой информации нет). Для объекта типа Process будет свой набор прав. А значит нужно проверять тип объекта (соответствующее поле в дескрипторе).

  2. Сборка мусора. Это всё же 80е, поэтому многие концепции сборки мусора на тот момент были наивны. Да и какие-то сложные алгоритмы запихать в крайне ограниченный объём микрокода было непросто. Поэтому управление памятью не сильно усложнено. Объекту может быть назначен уровень вложенности. Когда “функция” (конечно, в терминах 432 это называется совершенно по-другому, но я упрощаю) заканчивает исполнение и происходит возврат к вызывающему коду, процессор освобождает все объекты, которые соответствуют уровню вложенности этой функции. Также дескриптор содержит флаг того, используется ли объект кем-либо.

Кроме того, в дескриптор записывается разная служебная информация (не знаю, используется ли она в логике самого GDP или предназначена для внешних систем): отметки о том, был ли запрос на доступ к объекту, была ли запись в объект, содержит ли объект какие-то данные, или там все нули, и т.д. Если она не нужна процессору, то это дополнительный источник просадок по производительности - ведь эти поля синхронизируются по мере работы, и это лишние запросы к памяти.

Кстати, из кода можно заметить, что у некоторых объектов есть два сегмента - Data и Access. К примеру, ProcessorDataSegment / ProcessorAccessSegment. Что это значит? А это одно из весьма спорных решений Intel (которое поменяли в третьей ревизии iAPX 432). Объект разделён на 2 части - access сегмент содержит только указатели, а data сегмент всё остальное. То есть, работая с каким-то системным объектом, процессор зачастую обращается к 2м физическим сегментам. Редко какая операция требует доступ только к указателям или только к скалярным полям. При этом у нас удваивается количество обращений к памяти, ведь для чтения данных из сегмента нужно пройти двухэтапный путь получения адреса таблицы объектов и доступа к записям в этой таблице. Плюс ещё возможно потребуется записать какую-нибудь служебную информацию. Почему же у Intel получился такой непроизводительный процессор…

Не все, но многие объекты могут быть захвачены (залочены) либо железом, либо программно (через инструкцию LOCK OBJECT). Реализуется весьма просто - в data-сегменте объекта есть поле, которое содержит мета-информацию о блокировке: тип блокировки и идентификатор того, кто захватывает объект. Тоже крайне интересный концепт в 50-летнем процессоре, но что вы скажете об аппаратной поддержке очередей сообщений с поддержкой приоритетов, TTL, неблокирующих операций и ряда других плюшек?

Эта механика используется не только для пересылки сообщений между разными процессами, запущенными на GDP, но и самим планировщиком. Есть набор процессоров и есть набор процессов. Планировщик отвечает за то, чтобы найти процесс для исполнения на конкретном процессоре. Для этого используются очереди сообщений. В качестве потребителей выступают процессоры, в качестве сообщений - процессы, а публиковать сообщения в очередь могут разные источники, как внешние, так и внутренние.

выглядит просто

выглядит просто

При этом таких очередей несколько (прямо как в современном Linux’е, но в железе) - для обычных задач, задач реконфигурации, срочных процессов и диагностических. Есть даже очередь для спящих процессов, которые должны будут ожить в определённый момент (планировщик просто переместит его в очередь для обычных процессов). Причём очередь может быть как FIFO, так и основанная на приоритетах. Скажу честно, что я не особо экспериментировал в этой области - у меня всего один процесс для одного процессора, поэтому многие вещи я знаю только из теории, а не из практики. Разве что столкнулся с тем, что по истечении кванта времени, отведённого на процесс, планировщик вдруг захотел включиться в работу, а мне это было не нужно. Пришлось обмануть и занизить тактовую частоту счётчика тиков процессора - для iAPX 43202 нужно предоставить отдельный тактовый сигнал для внутренних нужд, он может быть произвольным и используется чисто в планировщике.

Если вкратце, то для запуска пользовательской программы необходимо подготовить начальный каталог, который содержит адреса таблиц объектов, сами эти таблицы и описать ряд основных объектов - процессор (фактически состоит из нескольких связанных сегментов), процесс, очереди, сообщения в этих очередях и сегменты кода/данных. Немало, но используя лог обращений к памяти, можно понять, какие именно поля являются критичными для процессора, и заполнить только их, игнорируя часть архитектурной сложности системы.

Организация пользовательского кода и данных

Исполняемый код процесса распределён по нескольким объектам типа Context. Упрощая, можно воспринимать это как функции или скорее процедуры. Контекст формируется из сегментов кода (объекты типа Instruction) и 4х списков с сегментами данных (списки называются entry access segments, EAS). Код может обращаться только к переменным, которые лежат в сегментах, определённых в EAS’ах. Так достигается некая изоляция - чтобы получить доступ к данным другого контекста, нужно вызвать соответствующую команду, которая импортирует EAS из другого контекста (конечно, если права доступа позволят).

Вызов процедуры (контекста) - очень накладная операция. iAPX 432 необходимо подготовить пачку объектов, описывающих контекст (не только соответствующие data и access сегменты, но и, к примеру, сегмент стека), что требует около 20–30 обращений к памяти. Это, кстати, одно из бутылочных горлышек программ на Ada, написанных для данной системы. Компилятор весьма неоптимально распределял код - в ISA есть инструкции для передачи управления на код, который располагается внутри контекста, так что не всегда было резонно создавать новый контекст и платить большую цену за вызов подпрограммы.

В моём низкоуровневом коде я использую только один контекст и один сегмент с кодом (больших программ я не писал, всё легко уместилось в 64Кб). Также было достаточно одного сегмента с данными (плюс стек), хотя EAS может содержать 16384 ссылок, что позволяет адресовать 4 x 16k x 64kb = 4Гб. Неплохо для проца из 80х.

Так как мне хотелось исследовать чистую производительность системы, я планировал писать код на “ассемблере”, а не пытаться найти трюки для Ada компилятора, чтобы выжать из него хоть что-то достойное. А значит нужно разобраться в программной архитектуре 432, прежде чем писать свои программы и транслятор, чтобы скомпилировать их в машинный код.

Кое-что я уже упоминал - отсутствие доступных регистров, за исключением одного 16-битного top-of-stack. И наличие стека (который, кстати, растёт вверх, а не вниз как в ARM или x86). Правда нет привычных инструкций PUSH/POP, но GDP сам модифицирует указатель на стек, если инструкция ссылается на данные оттуда.

Процессор поддерживает различные типы операндов - как обычные целочисленные (разрядностью до 32х бит!), так и числа с плавающей точкой (iAPX 432 одним из первых начал поддерживать числа в формате, который позже будет закреплён в спецификации IEEE 754). И, конечно же, есть инструкции для работы с разными объектами: системными (типа вышеуказанной “LOCK OBJECT”) и пользовательскими.

Формат машинного кода не подразумевает кодирования непосредственных значений, а значит большинство операндов - это ссылки на значения в памяти. Как же устроена адресация переменных? Увы, непросто - каждая ссылка состоит из двух частей: селектор сегмента данных, который содержит переменную, и смещение в данном сегменте. В простейшей форме селектор кодируется как индекс EAS’a в контексте (один из 4х) и индекс указателя на целевой сегмент. То есть даже самое тривиальное использование константы в коде превращается в несколько обращений к памяти. Э - эффективность. Есть ещё и косвенная адресация сегмента - это когда мы по селектору из машинного кода вычисляем адрес ячейки памяти, которая содержит ещё один селектор, который уже задаёт сегмент с нашей переменной.

Если вам кажется эта схема сложной, то ничего страшного - она действительно сложная...

Если вам кажется эта схема сложной, то ничего страшного - она действительно сложная...

Смещение переменной в сегменте данных тоже может быть более сложным, чем просто число. Базовый пример - обращение к элементу массива по индексу, который сам является переменной. Это возможно.

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

Кроме того, важный нюанс при написании транслятора заключается в том, что машинный код имеет формат инструкций переменной длины, причём они даже не выровнены по границам байт! То есть инструкция может кодироваться как шестью (6!) битами, так и двумястами (200!) битами. С точки зрения программирования это важно, потому что более короткие инструкции означают более компактный код и меньше обращений к памяти со стороны процессора, чтобы прочитать этот код.

Программы для iAPX 432

Как всегда, начнём с Hello world. Но как же нам что-то вывести на экран? У нас есть FPGA на шине с GDP, но как распознать команду процессора на отправку текста в консоль? Стандартный путь - это инициировать IPC через инструкции BROADCAST TO PROCESSORS или SEND TO PROCESSOR, но тогда нам придётся эмулировать ещё один процессор в системе, которому GDP посылает это IPC сообщение. К счастью, есть путь проще - команда MOVE TO INTERCONNECT просто записывает значение в межпроцессорный регистр, не требуя каких-то сложных подготовленных структур в памяти. Фактически это просто одна транзакция на шине, и FPGA может отловить запись по определённому адресу (GDP использует только 0x0 и 0x02, все остальные адреса в нашем распоряжении) и переслать данные через UART нашему ПК.

.stack {
  size = 0x10
  data = []
}

.data {
  msgIdx = { size = 2, data = [0x06, 0x00] }
  # reversed, because we start sending data from the end
  msg = { size = 12, data = [0x21, 0x64, 0x6c, 0x72, 0x6f, 0x57, 0x20, 0x6f, 0x6c, 0x6c, 0x65, 0x48] }

  # variables for sending data via UART
  interconnectRegUart = { size = 2, data = [0x02, 0x00] }
  interconnectSegmentSelector = { size = 2, data = [0x28, 0x00] }
}

sendTwoChars:
  MOVE_TO_INTERCONNECT interconnectSegmentSelector interconnectRegUart $data[msgIdx]
  # array is iterated in range [msgLen ... 1], because we want to reference uart payload from base 0
  # and need to skip element at index 0 (it's reserved for uart payload length)
  DEC_2U msgIdx msgIdx
  EQUAL_ZERO_2U msgIdx $st0
  BRANCH_FALSE $st0 sendTwoChars
  RETURN_FROM_CONTEXT

Код тривиален даже для людей, незнакомых с iAPX 432. Разве что поясню пару моментов.

Я ввёл псевдо-переменную $data как раз с целью оптимизации размера кода. Если мы адресуем переменную от начала сегмента, то это позволяет избежать кодирования смещения начала массива. Использовав msg[msgIdx], получим дополнительные 7 бит для кодирования 0x02 (смещение переменной msg в сегменте данных). В данном случае это экономия на спичках, но хотелось показать наглядный пример.

Можете обратить внимание на то, что условный переход выполняется за 2 инструкции - сначала сравниваем переменную с нулём и результат сравнения записываем в другую переменную (в данном случае - стек, а точнее top-of-stack регистр), а затем в зависимости от значения результата сравнения выполняем прыжок.

Hello world успешно получен

Hello world успешно получен

BRANCH_TRUE msgIdx sendTwoChars не работает, потому что BRANCH_TRUE работает с 8-битными значениями, а msgIdx - 16 бит. И дело не в том, что iAPX 432 проверяет разрядность, а просто в банальной арифметике - при касте 16-битного значения в 8-битное мы теряем часть информации и логика нарушается. Ранний возврат при msgIdx = 0x100 нам не нужен :)

Наконец-то переходим к тому, ради чего всё затевалось - написание бенчмарка. Как обычно, для этого я выбрал программу получения цифр числа Pi. А конкретно - кранинковый алгоритм (spigot). Он крайне простой для реализации, но позволяет сравнить производительность ALU.

.stack {
  size = 0x20
  data = []
}

.data {
  idx = { size = 2 }
  arr = { size = 55000 }                       # iteration in range [1 .. (LEN - 1)],
                                               # array length for 8192 digits is 27307, size should be 54614

  ### global variables

  toPrint = { size = 2, data = [0x00, 0x20] }        # amount of digits to print
  # toPrint = { size = 2, data = [0x00, 0x08] }
  # toPrint = { size = 2, data = [0x00, 0x01] }
  # toPrint = { size = 2, data = [0x0A, 0x00] }
  LEN = { size = 2, data = [0x00, 0x00] }            # length of array - 1
  nineCount = { size = 2, data = [0x00, 0x00] }      # count of consecutive 9s
  previousDigit = { size = 2, data = [0x02, 0x00] }  # previous digit

  ### local variables for inner loops
  carry = { size = 4 }
  denominator = { size = 4 }
  numerator = { size = 4 }
  digitFromCarry = { size = 2 }
  nextDigit = { size = 2 }

  ### constants
  c10 = { size = 4, data = [0x0A, 0x00, 0x00, 0x00] }  # constant 10
  c3 = { size = 4, data = [0x03, 0x00, 0x00, 0x00] }   # constant 3
  c2 = { size = 2, data = [0x02, 0x00] }   # constant 2
  c9 = { size = 4, data = [0x09, 0x00, 0x00, 0x00] }

  ### variables for sending data via UART
  interconnectRegTiming = { size = 2, data = [0x00, 0x00] }
  interconnectRegUart = { size = 2, data = [0x02, 0x00] }
  interconnectSegmentSelector = { size = 2, data = [0x28, 0x00] }
}

  MOVE_TO_INTERCONNECT interconnectSegmentSelector interconnectRegTiming c2

### initialization
  MUL_4U toPrint c10 $st0    # stk[0] = toPrint * 10, sp = 4 (toPrint is 2b, so LEN would be used as high part for operation)
  DIV_4U c3 $st0 $st0        # stk[0] = stk[0] / 3
  SAVE_4U LEN                # LEN = stk[0] (LEN is 2b, so high part, which is 0x0000, would be saved to nineCount)
  MOVE_4U $st0 idx           # idx = stk[0], sp = 0 (idx is 2b, so high part would be saved as first element for an array)
  MOVE_2U c2 $st0            # stk[0] = 2, sp = 2

init_array:
  SAVE_2U $data[idx]              # arr[idx] = stk[0]
  DEC_2U idx idx                  # idx--
  EQUAL_ZERO_2U idx $st0          # stk[2] = (idx == 0), sp = 4
  BRANCH_FALSE $st0 init_array    # if (stk[2] === false) goto init_array, sp = 2

  # XXX: only way to pop value from stack without extra access to memory
  BRANCH_TRUE $st0 main_loop     # sp = 0

main_loop:
  ZERO_4U carry                  # carry = 0

### computation loop
  MOVE_2U LEN denominator                       # denominator = LEN
  ADD_2U denominator denominator $st0           # stk[0] = denominator + denominator, sp = 2
  INC_2U $st0 numerator                         # numerator = stk[0] + 1, sp = 0

update_loop:
  CONVERT_2U_4U $data[denominator] $st0         # stk[0] = arr[denominator], sp = 4
  MUL_4U $st0 c10 $st0                          # stk[0] = stk[0] * 10
  ADD_4U $st0 carry $st0                        # stk[0] = stk[0] + carry
  SAVE_4U $st0                                  # stk[4] = stk[0], sp = 8
  REMINDER_4U numerator $st0 $st0               # stk[4] = stk[4] % numerator
  CONVERT_4U_2U $st0 $data[denominator]         # arr[denominator] = stk[4], sp = 4
  DIV_4U numerator $st0 $st0                    # stk[0] = stk[0] / numerator
  MUL_4U denominator $st0 carry                 # carry = denominator * stk[0], sp = 0
  DEC_2U numerator $st0                         # stk[0] = numerator - 1, sp = 2
  DEC_2U $st0 numerator                         # numerator = stk[0] - 1 (numerator -= 2), sp = 0
  DEC_2U denominator denominator                # denominator--
  EQUAL_ZERO_2U denominator $st0                # stk[0] = (denominator === 0), sp = 2
  BRANCH_FALSE $st0 update_loop                 # if (stk[0] === false) goto update_loop, sp = 0

### output digits
  MOVE_2U carry $st0                    # stk[0] = carry, sp = 2
  SAVE_2U $st0                          # stk[1] = stk[0], sp = 4
  GREATER_THAN_2U c9 $st0 $st0          # stk[1] = stk[1] > 9
  SAVE_2U digitFromCarry                # digitFromCarry = stk[1]
  BRANCH_FALSE $st0 nextDigit_computed  # if (stk[1] === 0) skip decrement, sp = 2
  SUB_2U c10 $st0 $st0                  # stk[0] = stk[0] - 10
nextDigit_computed:
  SAVE_2U nextDigit                     # nextDigit = stk[0]
  EQUAL_2U $st0 c9 $st0                 # stk[0] = stk[0] === 9
  BRANCH_FALSE $st0 print_digits
  INC_2U nineCount nineCount
  BRANCH main_loop
print_digits:
  ADD_2U previousDigit digitFromCarry $st0
  MOVE_TO_INTERCONNECT interconnectSegmentSelector interconnectRegUart $st0
  DEC_2U toPrint toPrint
  MOVE_2U nextDigit previousDigit
  EQUAL_ZERO_2U nineCount $st0
  BRANCH_TRUE $st0 check_done
print_nines_loop:
  # either output 0x0009, or 0x0000, based on digitFromCarry
  MOVE_TO_INTERCONNECT interconnectSegmentSelector interconnectRegUart c9[digitFromCarry]
  DEC_2U toPrint toPrint
  DEC_2U nineCount nineCount
  EQUAL_ZERO_2U nineCount $st0
  BRANCH_FALSE $st0 print_nines_loop
check_done:
  EQUAL_ZERO_2U toPrint $st0
  BRANCH_FALSE $st0 main_loop

### end of program
  MOVE_TO_INTERCONNECT interconnectSegmentSelector interconnectRegTiming c2
  RETURN_FROM_CONTEXT

Опять же, листинг не должен вызвать много вопросов, особенно с учётом ручного трассирования стека. Но отмечу несколько вещей:

  • Здесь наглядно видно, что iAPX 432 абсолютно не контролирует, как используются скалярные значения. Несмотря на всю свою объектно-ориентированность, GDP позволяет обращаться к int’ам как к short’ам. Да даже использовать их как массивы (взгляните на c9[digitFromCarry]).

  • Я нашёл только один способ для уменьшения указателя стека без дополнительного обращения к памяти: BRANCH_TRUE $st0 main_loop.

  • Ну и напоследок - деление и получение остатка это две разных операции, в отличие от других архитектур.

Замеры и выводы

Сравнение производительности разных процессоров, которые я тестировал

Сравнение производительности разных процессоров, которые я тестировал

На данный момент, iAPX 432 занял первое место в моём чарте по производительности. Оказался даже быстрее чем Intel 8080, который вычислял Pi по куда более продвинутому алгоритму Чудновского.

Но это не совсем честно - сравнивать процессоры разных поколений. Как насчёт современника - 8086? К сожалению, моя система на 8086 пока в нерабочем состоянии, но я воспользовался достаточно точным эмулятором (заявлена точность до такта) - 86Box. И оказалось, что iAPX 432 в среднем в 2.5 раза быстрее!

Как такое возможно? Этому результату способствовал ряд факторов - я преднамеренно избежал многих ловушек производительности, индуцированных компилятором Ada. Также, хоть ALU в iAPX 432 и 16-битное, производительность его на 16-битных и 32-битных операциях всё равно выше, чем у 8086. Ещё возможно повлияло то, что у меня нет дополнительных пропусков тактов при работе с памятью - она достаточно быстра, однако в эмуляторе я тоже постарался выбрать систему с похожими характеристиками, так что скорее всего данный фактор можно игнорировать.

Не скажу, что программировать под iAPX 432 мне понравилось, но вот сам путь к запуску своего кода на этой машине доставил мне удовольствие.

Все материалы (схема платы, gerber'ы, код для FPGA, код управляющей программы на ПК) можете найти в соответствующем репозитарии - https://github.com/quasiengineer/iapx432-sbc

Я записал несколько видео, в которых более подробно (по сравнению с текстовым форматом данной статьи) рассказываю о технических нюансах iAPX 432 и сопровождаю рассказ иллюстрациями и вырезками из различных доков. Если тема iAPX 432 интересна и не отторгает характерный славянский акцент в английской речи, то можете глянуть: