Радянські «Эльбрусы» — огляд архітектури

image
Про предмет статті ходить багато домислів — від «російський Барроуз» до «не має аналогів». Викликано це значною мірою відсутністю (доступною) повноцінної документації, нечисленним колом осіб, що мали з ними справу так і чималим часом, що пройшли з тих пір. «Ельбрус» перетворився в один з міфів минулої епохи.

З іншого боку, обчислювальний комплекс безсумнівно існував і показував відмінні для свого часу результати. Можливо, завдяки упокоренні елементної бази, яка примушувала розробників до выдумыванию різного роду архітектурних трюків. Багато з цих трюків зараз виглядають архаїчно, а деякі досить актуальні.

Так що автор з притаманною йому допитливості спробував розібратися з доступною документацією і скласти більш — менш цілісну картину. Якщо читачеві це цікаво — ласкаво просимо під кат.

Елементна база
Нове покоління техніки виникає не тоді, коли підросло нове покоління інженерів, а на основі якісних змін у технології. У тому, що стосується радянських «Эльбрусов», таким зміною стала поява інтегральних схем. Для «Ельбрус-1», це була 133 серія мікросхем з логікою ТТЛ (швидкодія 10...15 нс на вентиль). Для «Ельбрус-2» — 100-я серія з логікою ЭСЛ (швидкодія 2 нс на вентиль).

100 серія зроблена (серійний випуск з 1974 р.) на основі MECL 10K від Motorola (з 1971 р.) [6]. 133 серія (з 1969 р.) базувалася на 54 серії від TI (з 1965) [6]. Тобто якогось особливого відставання в елементній базі на той момент не було.

Організація пам'яті

Фізична пам'ять

Підсистема пам'яті будувалася на базі мікросхеми К565РУ3В NMOP [3] ємністю 16кбіт і організацією 16384х1.

Апаратура корекції та контролю дозволяє виправляти одинарні і виявляти подвійні помилки при зчитуванні інформації. Для цього при запису формуються і додаються до 72-розрядному інформаційному речі вісім розрядів коду Хеммінга. Таким чином, слово, збережене в пам'яті, містить 80 розрядів, включаючи: 8 — управляюча інформація (тег), 64 — дані, 8 — контрольні розряди коду Хеммінга.

Адресна шина має розрядність — 24, тобто розмір фізичної пам'яті обмежений 16 мега-словами (з 72 біта), 144 Мб даних разом з тегами.

Якщо рахувати разом з кодами Хеммінга, отримаємо 180 Мб або 90 Кмикросхем. Все це вдалося для максимальної комплектації розмістити у 8 шафах (секціях ВП).

У максимальну комплектацію входить 10 процесорів, при цьому кожен процесор з'єднаний з кожною секцією ОП. Довжина ліній зв'язку обрана приблизно рівною 10 м з умови розміщення пристроїв комплексу. Таку довжину світло пролітає у вакуумі за ~1/30000000 секунди (33 нс). В Е-2 ж сигнали передаються від вихідного регістра передавальної станції до регістра приймача за 80 нс (при робочому циклі в 44 нс), що дуже непогано.

Кожна шафа пам'яті (секція ВП) складається з чотирьох модулів пам'яті і комутатора. Модулі функціонально незалежні і можуть одночасно обмінюватись інформацією з різними процесорами. Всього 32 модуля, в кожному з половині мега-слова. При груповому поводженні всі чотири модуля запускаються одночасно. Це дозволяє отримати максимальний темп обміну даними з секцією ВП.

Цикл роботи модуля становить 13 тактів, однак послідовні звернення до різних мікросхем однієї групи можуть обслуговуватися кожні сім тактів.

На нижньому рівні, мінімальний розмір блоку — 16К слів відповідає обсягу мікросхеми пам'яті 16К біт. Блок складається з 80 мікросхем пам'яті, по одній мікросхемі на розряд. Отже, модуль пам'яті складається з 32 таких мінімальних блоків.

Пристрій обміну з оперативною пам'яттю (УТОП).

Є частиною процесора.

Має окремий канал зв'язку з кожної з восьми секцій ОП і може звертатися в кожну секцію як з одиночними, так і з груповими запитами.

При груповому запиті за одне звернення відбувається обмін чотирма словами. Час звернення по запису в пам'ять становить для УТОП близько восьми тактів, час звернення зчитування пам'яті — близько 20 тактів (звернення може бути затримано комутатором ВП у випадку зайнятості модуля пам'яті або конфлікту з поводження з іншим ЦП).

Одночасно в УТОП може зберігатися до трьох запитів. УТОП має внутрішнє розпаралелювання на два канали, що дозволяє виставляти одночасно два запиту на обмін в різні секції ВП. Максимальна частота зміни в каналі — один запит за вісім тактів. Таким чином, при групових звернення до пам'яті УТОП забезпечує темп обміну — одне слово за такт [1].

Для оптимізації роботи при послідовному обході адрес використовується техніка розщеплення (interleaving). Частина бітів у фізичному адресу перемішується, в результаті послідовно йдуть слова виявляються в різних одиницях вищеописаною ієрархії пам'яті і можуть читатися паралельно. Схема перемішування така:
image
При виключенні окремих модулів конфігурації, операційна система не призначає їх адреси. Наявність вимкненого модуля робить неможливим розщеплення в даній секції.

Математична (віртуальна) пам'ять
Математична пам'ять описується 38-розрядним математичним адресою за умови зазначення розміщення величини з точністю до одного розряду (32-розрядним адресою при вказівці з точністю до машинного слова).

Величини

Основною одиницею інформації є величина. Величина -логічна одиниця інформації, до якої можна адресуватися за допомогою математичного адреси. Над величинами ж виробляються операції при виконанні команд програм.
Величини можуть мати наступні розміри в розрядах (формати): 1, 4, 8, 16, 32, 64, 128.

Ні одна з величин крім 128-розрядної не може бути розташована більш ніж в одному слові.

В одному слові можуть перебувати величини лише однакової типу і формату за одним винятком: ціле, дійсне формату 32 розряду можуть бути упаковані в одному слові в будь-якому порядку.

В керуючих розрядах слова зберігається інформація про тип і формат величин, що знаходяться в слові.

Сторінки

Математична пам'ять розбита на сторінки. Сторінки можуть бути різних розмірів (кратних 16) — від 16 до 1024 слів. Адреса початку сторінки має молодші 10 (38) розрядів нульовими [2 стор. 61].

Блок з 16 слів, розташований в математичної пам'яті так, що в 38-розрядному адресу першого слова молодші 10 розрядів нульові, називається рядком.

Сегменти

Сегменти області безперервної адресації, що виділяються системою.
Початок будь-якого сегменту математичної пам'яті збігається з початком деякої сторінки.

Для розміщення сегменту використовується сторінка мінімально можливого розміру.
Сегменти розміром більше 1024 слів розміщуються математичної пам'яті на суміжних сторінках, при цьому всі сторінки, крім останньої, повинні мати максимальний розмір.

Початок сторінки, а отже і сегмента, у фізичній пам'яті може розміщуватися на початку будь-якої фізичної рядка. Сторінка розташований у суміжних комірках фізичної пам'яті.

Тому сегмент розміром більше 1024 слів може бути не повністю фізичної пам'яті (swapping), а його сторінки у фізичній пам'яті не зобов'язані йти підряд.

Підтримка віртуальної пам'яті.

Віртуальна пам'ять організована досить логічним і природним чином.
Сегменти створюються ядром ОС, що неминуче, враховуючи теговую систему і питання захисту пам'яті.

Всі вільні ділянки пам'яті в ядрі об'єднані в упорядкований список вільних областей. Упорядкування проводиться за розміром області. У системі команд є спеціально для цих цілей призначена команда «ПОШУК ЗА СПИСКОМ», яка знаходить або область, точно збігається за розміром з необхідним, або мінімально перевершує за розміром необхідний розмір. У першому випадку знайдена область виключається зі списку, завантажується в математичну пам'ять (якщо це потрібно) і як результат процедури формується дескриптор цій області. У другому випадку, якщо розмір знайденої області перевершує необхідний більш ніж на 15 слів, знайденої області забирається область потрібного розміру, а залишок повертається в область пам'яті, де він буде включений у відповідне місце списку вільної пам'яті. Користувачеві видається дескриптор на область пам'яті розміром, у точності збігається з запитуючою.

У тому випадку, якщо пам'яті необхідного розміру немає, відбувається спроба консолідації ділянок вільної пам'яті шляхом переміщення зайнятих ділянок меншого розміру, що розділяють сусідні вільні області, наявні вільні області.

Якщо це не приводить до мети, відбувається відкачування (свопування) зайнятих ділянок на зовнішні носії. Це не стосується виконуваних сегментів, їх можна просто звільнити…

Кожна задача має свій файл відкачування, а кожен сегмент знає своє завдання. Розподіл вільної пам'яті у файл виробляється на бітовій масці[2, стор 143], в якій один розряд відповідає сторінці. (Для математичної пам'яті розміром 2**32, потрібно маска в 2**(32-16)=64К на завдання, що не так вже й мало. Крім того, незрозуміло, як бути зі сторінками розміром менше максимально можливих 8К.) Таким чином вдається уникнути проблем з підтриманням реєстру вільних сторінок у порівнянні з загальним файлом відкачування. Адже кожна задача має свою математичну пам'ять. Крім того, вивільнення пам'яті процесу зводиться до видалення файлу його відкачування, робота перекладається на файлову систему. При відносно невеликому числі завдань це відмінний підхід. До його недоліків можна віднести хіба що додаткове навантаження на файлову систему.

В ядрі ОС є задача, яка у фоновому режимі «підстригає» завдання [2, стор 143], скидаючи деякий відсоток сторінок. Дані пасивних завдань при цьому поступово виявляються у файлах відкачування, що активно працюють завдання балансуються.
Розберемо тепер перетворення адреси математичного у фізичний.
Всі області пам'яті задачі (сегменти) об'єднані списком, голова і хвіст якого розташовані в дескрипторі завдання. Зазначимо, що в даній архітектурою не передбачено звернення по голому вказівником в дусі «C», завжди перетворюється пара (покажчик, зміщення).

У згаданому списку сегментів завдання з допомогою інструкції «ПОШУК ЗА СПИСКОМ» знаходиться описувач сегмента, а в ньому фізичну адресу потрібної сторінки. Якщо у фізичній пам'яті дана сторінка відсутня, включається механізм підкачки.

Для прискорення перетворення математичного адреси у фізичний в ЦП для найбільш «активних» сторінок передбачений асоціативний масив розміром в 64 елемента. Асоціативною ознакою є номер математичної сторінки, відповідь — фізичний адресу початку сторінки, її розмір, а також деякі службові ознаки. Час пошуку в цьому асоціативному масиві — 3 такту, темп обробки — одна заявка в такт.

Крім того, саме слово, до якого відбувається обіг, разом зі своїм математичним адресою поміщається в асоціативну пам'ять слів, яка також є у кожного ЦП. Така пам'ять є аналогом сучасної кеш пам'яті.

Є згадка [2, стор 144] про таблиці откачанных сторінок (ТОС), призначеної для розміщення інформації про откачанных або ще не ініціалізований сторінках. Голова цієї таблиці знаходиться в ОЗУ, переповнення — у зовнішній пам'яті (барабані). Її величина пропорційна кількості існуючих сторінок, а не всієї математичної пам'яті.

ТОС одна на систему, значить для кожної сторінки треба запам'ятовувати ще й ідентифікатор завдання.

Для прискорення роботи з ТОС в ядрі передбачений кеш, причому, як стверджується, досить ефективний. Але це дивно, адже для ефективного кешування необхідно розуміти логіку роботи прикладної задачі з пам'яттю, а вона розробникам ОС невідома.

Отже:
  • Сторінки відкачуються на зовнішні носії фонової завданням ядра, «подстригающей» завдання. Тому в ТОС вони йдуть пачками та (ймовірно) в упорядкованому вигляді.
  • Вільне місце у файлах відкачування розподіляється за допомогою згаданої бітової маски.
  • Підкачуються ж сторінки в довільному порядку, обумовленому логікою роботи прикладних задач.
  • Під ТОС віддали найшвидшу (і найменшу — до 136 Мб) зовнішню пам'ять — барабанысередній час доступу 5-5,5 мс, темп обміну 3.6 Мб/с [2, стор 187]. В якості порівняння, для дисків середній час доступу 60 мс, темп обміну — 180 Кб/с.
  • Тим більше прикро, якщо в процесі роботи ТОС виявиться фрагментованою і буде вимагати надлишкового місця. Крім того, з часом ТОС втратить впорядкованість і для пошуку потрібної сторінки її доведеться читати цілком.
  • По всій видимості, хоч ТОС називається таблицею, вона влаштована як Б-дерево (1970р.) або, наприклад, аналогічно хеш-таблиці DBM (1979 р), дешево і сердито. Швидше перше, враховуючи, що дерево-образна структура використана для розподілу вільної пам'яті файловою системою [2, стор 146]. З іншого боку, в [2, стор 200] можна побачити жалю з приводу того, що управління сегментами вийшло занадто складним і спричинило за собою застосування відмінного від прийнятого в ЄС ЕОМ інтерфейсу обміну ОЗП із зовнішньою пам'яттю. З усіма витікаючими наслідками.


Типи даних
Як вже говорилося, слово складається з 64 розрядів даних і 8 керуючих розрядів. Керуючі розряди містять тег — опис того, що лежить в інформаційній частині слова.
Це може бути:
  1. Порожньо формату 32 і 64 біта — зазвичай це неініціалізовані дані, спроба прочитати що веде до виключення.
  2. Ціле формату 32 і 64 біта — на відміну від поширеного нині формату, тут знак виділений в старший біт.
  3. Речовий 32 — знак мантиси/знак порядку/лад 6 розрядів/мантиса 24 розрядів
  4. Речовий 64 — знак мантиси/знак порядку/лад 6 розрядів/мантиса 56 розрядів
  5. Речовий 128 — знак мантиси/знак порядку/лад 6 розрядів/мантиса 56 розрядів/старші розряди порядку — 8 розрядів/старші розряди мантиси — 56 розрядів
  6. Біт
  7. Цифра — 4 розряду, зазвичай використовується для представлення десяткових чисел
  8. Байт — 8 біт, зазвичай — символ рядка
  9. Набір — 64 розряду, сукупність однотипних елементів, які не є величинами і індивідуальний доступ до них неприпустимий (SIMD?)
  10. Дескриптор — 64 розряду, описувач області пам'яті, створюється тільки ядром, що містить математичний адресу, розмір в елементах, розмір елемента в розрядах, тип захисту пам'яті. Максимальний розмір області, описуваної дескриптором, дорівнює 2**19 слів.
  11. Індексне (непряме) слово — 64 розряду, зазвичай застосовується в циклах для послідовної адресації елементів масиву. Містить значення індексу, максимальне значення і крок збільшення.
  12. Інтервал — 64 розряду, застосовується для отримання дескриптора під-області з області, описуваної індексовані дескриптором.
  13. Мітка операції переходу (goto) — 64 розряду, містить
    • математичний адресу початку області — 32 розряду
    • номер байта від початку області — 16 розрядів

    • дескриптор програмного сегмента — 12 розрядів
    • лексикографічний рівень процедури — 4 розряду (0 — системні виклики, 1&2 — користувальницькі процедури. 3 — перший виконуваний… [2, стор 43]).
  14. Семафор — засіб синхронізації в системі.
Особливості системи команд
  1. Дані забезпечені тегами, це дозволяє апаратно захищати їх а також спростити систему команд, наприклад, замінивши ціле сімейство типізованих команд додавання на одну єдину — з урахуванням тегів
  2. Прийнята безадресна система команд, в результаті чого відсутня зайва інформація про розподіл регістрів. Ефективне динамічне розподіл регістрів реалізує апаратура. Безадресний формат команд базується на апаратній реалізації стека.
  3. Адресація даних в командах відбувається по парі — сегмент/індекс з урахуванням контексту виконання, а не з математичного адресою. Наприклад, порядковий номер в області стека, відведеної для конкретної процедури.
  4. Для виклику функцій крім математичного адреси використовується досить докладний дескриптор, у якому крім усього іншого вказано розмір локального стека під дані і параметри. Реалізовано відкладене виділення даних, при вході у функцію створюється тільки дескриптор сегмента, власне виділення відбувається при першому зверненні. Повідомляється [2 стор. 42], що, це новаторська техніка. У самому справі, номер дескриптора функції йде аргументом виклику так само як і математичний адресу коду. У x86, наприклад, всю допоміжну роботу робить компілятор, розміщуючи пролог і епілог функції.

Безадресний код.

Ми говоримо «безадресна система команд», маємо на увазі — "стековий процесор".
Розглянемо простий приклад, обчислення виразу x+y*z+u.
При побудові дерева розбору компілятором воно виглядає так:
image
Асемблерний код (x86) для цього виразу:
mov eax,dword ptr [y]
imul eax,dword ptr [z]
mov ecx,dword ptr [x]
add ecx,eax
mov eax,ecx
add eax,dword ptr [u]
Для гіпотетичної стекової машини псевдокод такий:
push x
push y
push z
multiply
add
push u
add
Для роботи зі стеком необхідні операції наступних типів:
  • запис елемента на вершину стека (push)
  • видалення елемента з вершини стека (pop)
  • бінарні операції, видаляють два елементи з вершини стеку і записують результат операції (+-*/)
  • унарні операції, заміщають результат на вершині стека (зміна знака)
Що нам пропонує архітектура Ельбруса?
  1. Операції завантаження в стек. Інформація може завантажуватися в стек або безпосередньо з командного потоку, або з області даних. У першому випадку завантажувана величина знаходиться в самій команді, у другому випадку величина завантажується за адресою, що знаходиться або в команді, або у верхівці стека (завантаження за адресою).

    Зазначимо, що оскільки стек реалізований як циклічний буфер з регістрів, завантаження нового елемента може викликати автоматичне вивантаження частині стека в пам'ять.

    Особливий інтерес представляє завантаження за адресою, при якій:
    • за адресною парі знаходиться математичний адреса величини
    • при цьому перевіряється можливість доступу до величини
    • величина завантажується у верхівку стека
    • далі виконується цикл
      • по тегу перевіряється клас значень завантаженої величини
      • якщо величина належить класу значень, операція завершена

      • якщо це адресна пара, кидається виключення
      • якщо це непряме слово, відбувається зчитування за непрямим слова
      • якщо це мітка процедури, викликається процедура, яка повинна залишити свій результат на вершині стека
  2. Запис в пам'ять з стека. Для цього потрібні дві величини з верхівки стека: величина і адреса запису. В якості адреси запису може виступати або адресна пара, або непряме слово. Запис проводиться «одношагово», тобто безпосередньо за адресою запису.
  3. Арифметичні операції, операції відношення та логічні операції. Арифметичні операції виконуються над однією або двома величинами, що містяться у верхівці стека. Як правило, операнди є цілими чи дробовими, причому, якщо операнди різного типу (ціле і речовий), то перед операцією проводиться автоматичне перетворення цілого в дійсне. Якщо операнди мають різні формати, то перед операцією проводиться автоматичне вирівнювання довжин» по найбільшій довжині.

    Вихідні числа прибираються з стека (з відповідним зміщенням покажчика), а потім результат поміщається у верхівку стека.
    Одномісні арифметичні операції беруть операнд з верхівки стека і замінюють його на результат.


Центральний процесор.
image
Структурна схема ЦП [1]:
  • пристрій обміну з оперативною пам'яттю (УТОП)
  • буферна пам'ять команд (БК) об'ємом 512 слів для узгодження темпу дешифрування команд і виклику програми з оперативної пам'яті
  • пристрій управління (УУ)
  • 10 спеціалізованих виконавчих пристроїв (ВП) для виконання команд
  • базові регістри (БР) для перетворення відносних адрес в абсолютні
  • стек операндів (СтОп) об'ємом 256 слів для зберігання проміжних результатів операцій
  • буферна пам'ять (БП) 1024 слова, призначена для зберігання глобальних даних плюс буферна пам'ять масивів на 256 слів з попередньою підкачкою
  • СВ – схема вибірки (декодування)

Декодування.

Програмний код вичитується в буфер команд (БК). БК працює як асоціативний масив, має обсяг від 512 слів, які поділені на 32 рівних сегмента. Підкачка інформації в БК з оперативної пам'яті проводиться блоками по чотири слова. Темп довантаження визначається необхідним запасом командних слів у буфері команд і автоматично регулюється при зміні темпу декодування команд.

Схема вибірки отримує чергову команду з БК, розшифровує і передає в УУ для перетворення у внутрішній регістровий формат.

Система внутрішньої адресації.

Всі передачі адресної та числової інформації супроводжуються в ЦП внутрішнім адресою джерела або споживача інформації. Внутрішні адреси присвоєні буферу команд, стеку операндів (СтОп), БЖ та деяких вхідних регістрів ІУ.

СтОп складається з 32 регістрів і 32-розрядного регістра — бітової маски. Кожний біт в цій масці відповідає за конкретний регістр, 1 — означає зайнятий, 0 — вільний.

При видачі команди в ІУ формуються внутрішні адреси операндів і результату операції. Для операцій зі стеком, операнди і результат — номери регістрів з СтОп.

Для декодування команди підсумовування, наприклад, вважається, що номери операндів вже отримані раніше, можна призначати будь-який вільний регістр з СтОп.
Після того як команда відчула, операнди звільняються.

Виконання.

Не всі команди можуть бути виконані безпосередньо після їх декодування, тому необхідний буфер декодованих, але не виконаних команд. ЦП володіє таким буфером на 32 команди у внутрішньому поданні.

З декодера команди видаються в послідовності, заданої програмою. При цьому вони послідовно нумеруються і цей номер супроводжує команді все її життя. Цей номер — індекс у згаданому буфері декодованих команд, коли він доходить до 32, то знову починає з нуля. Знаючи поточне значення, можна вибудувати всі команди в черзі за порядком їх декодування.

Час виконання команд в ІУ визначається їх типом і коливається від 2 до 20 тактів для операцій над операндами одноразової точності (32) і до 30 тактів над операндами подвоєною точності (64). ??? Множення(Ф64) — 4 такти. Поділ (Ф64) — 24 такту.

Виконання операцій над речовинними числами формату Ф128 вимагає триразового складання — складання молодших розрядів, складання старших розрядів з урахуванням перенесення з молодших розрядів, повторного складання молодших розрядів з урахуванням перенесення зі старших розрядів. Для цього потрібні станції зберігання проміжних результатів.

Виконуватися команди починають по готовності аргументів і доступності потрібного виконавчого пристрою. Т. к. порядок виконання не обов'язково збігається з вихідним, ми маємо позачергове виконання (OoO). А оскільки виконавчих блоків кілька і вони працюють паралельно, наявності суперскалярность. Правда, суматор, дільник і помножувач в єдиному екземплярі, але можуть працювати паралельно.

Враховуючи, що всього є 10 виконавчих пристроїв, виникає питання, а скільки ж інструкцій за такт може бути виконано. В [2 стор 185] згадується, що в технологічному режимі з послідовним виконанням команд продуктивність падає в 2-3 рази. В [4] йдеться про 2 команди за такт. Це пов'язано, мабуть, з реалізацією СтОп, яка допускає тільки два записи за такт.
Приклад
Для демонстрації можливості цілком суперскалярного поведінки, розглянемо вираз(a * b) + (c / d)". При цьому змінні a" & «c» розташовані в області локальних змінних процедури і присутні в кеші, а «b» & «d» глобальні та в кеші відсутні. Для простоти, всі вони типу F64.
Асемблерний код виглядає приблизно так:
ВЗ а
ДВЗ b

ВЗ c
ДВЗ d

+
Де ВЗ означає «викликати значення», ДВЗ — «довга ВЗ», а мнемоніки арифметичних операцій невідомі.

Після декодування код у внутрішньому поданні приблизно такий (мнемоніки придумані):
загр a -> R1; з захопленням R1 в СтОп
загр b -> R2; з захопленням R2 СтОп
∗ R1 R2 ->R3; з захопленням R2 СтОп, R1 & R2 звільняються за виконання
загр з -> R4; з захопленням R4 в СтОп
загр d -> R5; з захопленням R5 в СтОп
⁄ R4 R5 ->R6; з захопленням R6 в СтОп, R4 & R5 звільняються за виконання
+ R3 R6 ->R1; з захопленням звільненого було R1 в СтОп,
; R3 & R6 звільняються за виконання
; результат в R1
З-за залежностей між командами виконання відбувається у наступному порядку (2 інструкції за такт, завантаження з кеша — 1 такт, завантаження з пам'яті 13 тактів, "*"-4 такту, "/" — 24 такту, "+" — 3 такту):
такт Старт Виповнилося
1 загр a -> R1; загр b -> R2
2 загр з -> R4; загр d -> R5 загр a -> R1
3 загр з -> R4
13 загр b -> R2
14 ∗ R1 R2 ->R3 загр з -> R4
15 ⁄ R4 R5 ->R6
17 ∗ R1 R2 ->R3
38 ⁄ R4 R5 ->R6
38 + R3 R6 ->R1
40 + R3 R6 ->R1
Множення і ділення виконуються цілком паралельно. На жаль, але арифметичні виконавчі пристрої не мають конвеєрів, які планувалися лише у векторному процесорі [2, стор 150].

Зупинка та продовження виконання

Іноді може знадобитися призупинити роботу програми з можливістю при необхідності продовжити роботу, наприклад, у разі закінчення кванта часу. Для цього потрібно відновлення початкового порядку інструкцій. При цьому
  1. запам'ятовується номер команди, що викликала переривання
  2. завершуються команди, дешифровані раніше неї, і скасовується виконання команд, дешифрованих пізніше
  3. відновлюється стан регістрів на момент, відповідний декодуванню команди, що викликала переривання
  4. входимо до переривання
Відновлення стану регістрів потребує спеціального механізму, наприклад, циклічного буфера для лода змін значень регістрів розміром 2 * «максимальна довжина інструкції в тактах». В цей лог при завершенні інструкції записуються — старе значення регістра з СтОп, нове значення, номер регістру і номер команди. Тоді при необхідності можна відкрутити назад зміни для отменяемых команд.

Скасування виконання команд використовується також при обробці команд умовного переходу. Таким чином здійснюється спекулятивне виконання коду.
Про провісник напрямку розгалуження є згадка [1], а механізм незрозумілий.

Отже, що ми маємо?
  • суперскалярність на рівні 2 інструкції за такт
  • для реалізації суперскалярности використана техніка scoreboarding, ніякого перейменування регістрів, як зазначено в [5], немає і близько, ця техніка тут просто непридатна
  • використання стека операндів близько до техніки регістрових вікон, з'явилася незалежно, але на кілька років пізніше (Е-1: розр. 1973-1979, BerkeleyRISC: 1980-1984)
  • позачергове виконання команд
  • пророкування переходів зі спекулятивним виконанням
  • поєднання безадресної архітектури та суперскалярности в Ельбрусі-1 мабуть має пріоритет

Багатопроцесорна робота

Проблема межпроцессорного взаємодії пов'язана з наявністю у кожного процесора свого СОЗУ (кеш) і, отже відповідністю даних в ОЗП і СОЗУ. СОЗУ дозволяє значно знизити частоту звернень у відносно повільну пам'ять, але при цьому можлива ситуація, коли дані в пам'яті свіже оних в СОЗУ.
У МВК «Ельбрус» завдання може породжувати декілька процесів (threads), що розділяють загальну математичну пам'ять. Ділянку програми, що використовує (що зчитує або модифікувальний), загальні для кількох процесів дані, називається критичною секцією.

Проблем дві — синхронізація та підтримка когерентності кеш.

Семафори:
  • Семафор — це тип даних в системі команд, який має свій тег [2 стор 121]. Непривилегированный користувач не може ніяк змінити вміст семафора. Він може лише подавати семафор по імені у якості параметра процедури синхронізації (семафори не можуть копіюватися і кожен семафор існує лише в одному екземплярі, проте на кожен семафор може бути встановлено кілька посилань)
  • Семафор може бути в стані «відкрито» або «закрито».
  • Функція «ЧЕКАТИ» (з аргументом — посиланням на семафор) блокує (ставить його в чергу семафора) поточний thread, якщо той закритий або нічого не робить, якщо відкритий. Після розблокування, thread продовжить виконання з наступного після виклику цієї функції інструкції.
  • Функція «ВІДКРИТИ» (...) переводить семафор у відкритий стан, всі чекають його відкриття процеси (threads) переносяться в чергу готових до виконання
  • Функція «ПРОПУСТИТИ» (...) переводить всі процеси (threads), заблоковані на даному семафорі в розряд готових до виконання, семафор залишається закритим
  • Функція «ЗАКРИТИ» (...) переводить семафор в закритий стан, якщо він був відкритий. Якщо семафор був закритий, то поточний thread блокується і ставиться в чергу семафора. Відміну від функції «ЧЕКАТИ» в тому, що коли даний thread розблокується, знову буде зроблена спроба закрити семафор.
  • Функція «ОТКЗАКРЫТЬ» з двома аргументами — посиланнями на семафори, атомарно відкриває перший семафор і закриває другий. виробник-споживач. Наприклад, процеси обмінюються порціями даних, при споживач повинен отримувати сигнал, що готова нова порція даних, а виробник — споживач обробив попередню. Рішення цієї задачі на двох семафорах полягає в тому, що один семафор використовується для оповіщення виробника, а інший — споживача.
  • Для привілейованих користувачів доступні операції спинлоками — «ЖУЖ»(так, від слова дзижчати [2 стор. 124]) і «ОТКРСЕМ», аналогічні «ЗАКРИТИ» і «ВІДКРИТИ». Ці операції помітно швидше, але придатні тільки для короткочасних робіт, при цьому неприпустимі переривання (робота з віртуальною пам'яттю і пристроями). Семафор не можна одночасно працювати і як семафор і як з спинлоком.
  • На користувачів покладається відповідальність за правильністю використання цієї техніки. Наприклад, якщо якийсь процес відкриває семафор, який їм не закривався, то процес, який закривав семафор, втрачає монополію на роботу з даними, що охороняються цим семафором. Крім того, ця техніка не гарантує відсутності тупиків.
  • При закритті семафора запам'ятовується час цієї події, воно нам ще придасться.
Когерентність пам'яті.
Вважається, що робота з загальними даними може проводитися тільки в критичній секції, тобто між операціями «ЗАКРИТИ» і «ВІДКРИТИ» або операціями «ЖУЖ» і «ОТКСЕМ».

Всі дані, записані в СОЗУ процесора між цими операціями, позначаються як «загальні» і, крім того, запам'ятовується час їх занесення до СОЗУ. Під СОЗУ розуміється пам'ять глобальних даних об'ємом 1024 слова [2 стор. 186].

Кожна процедура має своє власне локальне адресний простір, глобальні дані належать іншим процедурам, наскільки це дозволяє область видимості процедури. Тобто для компілятора і для процесора звернення до локальних і глобальних даними нерівноцінні.

При відкритті семафора в нього записується час відкриття. При кожному закриття семафора аналізується записане в нього час і час запису загальних даних у СОЗУ. Ті загальні дані, які потрапили до СОЗУ раніше відкриття цього семафора, тепер не достовірні, оскільки могли бути змінені іншими ЦП. Ті дані, які потрапили до СОЗУ після відкриття семафора, достовірні, так як вони не мають відношення до даного семафору.

Розберемося на прикладі. Припустимо, мова йде про потоко-безпечному лічильнику.
  1. потік-1 захоплює семафор-1 на ЦП-1 в момент Т-1
  2. потік-1 читає змінну лічильник-1, якої не було в глобальному кеші
  3. потік-2 намагається захопити семафор-1 і блокується
  4. тепер лічильник-1 з'явився в глобальному кеші ЦП-1 згодом Т-2
  5. потік-1 нарощує і записує лічильник-1 в глобальний кеш ЦП-1 згодом Т-3
  6. потік-1 закриває семафор-1 згодом Т-4. У цей момент виявляється зміна лічильник-1 в межах часу захоплення семафора-1 і це значення записується в пам'ять, тепер воно доступне всім
  7. потік-2 прокинувся в момент Т-5 на ЦП-2 з захопленим семафор-1
  8. потік-2 намагається прочитати значення лічильник-1, яке вже є в глобальному кеші ЦП-2 з позначкою часу Т-0. Т. к. Т-0 менше Т-5, значення вважається інвалідним і читається з пам'яті.
  9. потік-2 закриває семафор-1 згодом Т-6. Оскільки нічого не змінювалося, нічого не відбувається
Таким чином, на підставі порівняння часу відкриття семафора і часу занесення загальних даних у СОЗУ, ЦП однозначно вирішує проблему достовірності даних. При цьому не потрібно скидати весь глобальний кеш, що завдало б удару по продуктивності.

Щоб забезпечити все це, для між-процесорного взаємодії введені інструкції:
  • «ПРЕРВАТЬЦП» з аргументом — маскою зацікавлених процесорів. В результаті всі зазначені процесори отримують переривання.
  • «ЖДАТЬЦП» — виконання команди полягає в очікуванні виконання команди «ОТВЕТЦП» від всіх процесорів, зазначених у команді (параметрі команди)
  • «ОТВЕТЦП» — підтвердження отримання і виконання переривання
По всій видимості, так поширюється інформація про зміну стану семафорів.

Також імовірно, таким чином повинні поширюватися дані, які заносяться до СОЗУ апаратно без закриття будь-яких семафорів. Сюди відносяться дескриптори програмних сегментів та таблиці відповідності математичних і фізичних адрес.

Таким же чином можна було б вирішити проблему доставки змін до СОЗУ при закритті семафора до СОЗУ інших процесорів.

Виклик функцій
Параметри виклику функції та локальні змінні лежать в стеку. Контекст виклику також лежить в стеку. В сутності, контекст складається з двох покажчиків — на старий контекст (тобто контексти пов'язані списком для здійснення виходу із функцій) і на інструкцію, з якої відбувся виклик. Плюс додаткова інформація, все це вдалося упакувати в два слова.

Але виклик функцій в Ельбрусі своєрідний. Він багатофазний.
  1. Спочатку ми повинні створити контекст майбутнього виклику і помістити його на вершину стека (маркування стека)
  2. Обчислюємо параметри виклику, які виявляються на вершині стека в потрібному порядку. Таким чином параметри знаходяться після контексту і до них можна звертатися через дескриптор області локальних даних використовуючи загальний механізм апаратного захисту.
  3. Виділяється пам'ять (розмір якої визначає компілятор) під локальні дані. І все, можна починати виконання функції.
При виході, якщо необхідно, функція на місці свого колишнього контексту залишає обчислене значення.

Навіщо такі складнощі? Чому не викликати функцію більш традиційно, однією інструкцією?

В цьому випадку параметри функції опиняться перед контекстом і дескриптор області локальних даних, який починається з цього контексту не зможе їх покрити. Якщо ж зрушити початкову область дескриптора назад, на початок області параметрів, зламається список стека викликів, або доведеться прописувати в стек псевдо-параметр -довжину області параметрів. Що фактично та ж сама маркування стека.

Що стосується згаданого дескриптора для доступу до локальних даних, то він не один, їх аж 32. І який з них використовується, визначається лексикографическим рівнем функції.

Лексикографічний рівень процедур.

Цей механізм в чомусь схожий на рівень привілеїв х86. Є 32 базових регістра дескрипторів, що описують локальні дані процедур (+параметри) в стеку.
Кожен (як звичайний дескриптор області) містить:
  1. 32...38 розрядів — математичний адресу початку описуваної області («МАНАЧ»)
  2. 13...18 розрядів — максимальний індекс або розмір («ИНДМАКС»)
  3. 3 розряду — крок дескриптора 2**n біт (n = 0,1,2,...,7) («ШАГДЕСК»)
  4. 4 розряду — інформація про режим доступу («ЗАХИСТ»)
Різні базові регістри описують різні контексти стека. Так, реєстр №0 містить дескриптор, що описує стек з описателей процедур і даних операційних систем, регістри №1 і №2 описують стеки описателей процедур і зовнішніх імен виконуваної задачі, реєстр №3 містить дескриптор, що описує стек блока верхнього рівня і так далі.

Таким чином, якщо деяка інструкція інструкції містить закодоване ім'я величини (фізично розташованої в стеку), то воно визначено тільки в даному контексті. Доступ до елемента стека здійснюється через лексикографічний рівень функції, і порядковий номер слова в області стека, виділеної для даних цієї функції (адресна пара).

ФОРТРАН, КОБОЛ, ЛИСП, APL — мови, які не мають блокової структури

У процедурах таких мов доступні лише локальні змінні і параметри виклику. Розподіл пам'яті для кожної викликається процедури проводиться динамічно. При вході в процедуру їй відводиться в стеку необхідна область вільної пам'яті для формальних параметрів і локальних змінних, покажчик стека (УС) пересувається на нову кордон.

Відводиться процедурі область пам'яті описується дескриптором, поміщених в один з базових регістрів (нехай N). Звернення до величині в цій сфері здійснюється з допомогою адресної пари (N, I).

Область вільної пам'яті в стеку нижче нової кордону використовується для зберігання проміжних результатів.

При виході з процедури, відведена їй область пам'яті повертається назад в область вільної пам'яті шляхом відновлення попереднього ВУС. Таким чином, кожній процедурі відводиться рівно стільки пам'яті, скільки зажадає динаміка її виконання, і тільки тоді, коли це необхідно.

Якщо з розглянутої процедури Р провадиться звернення до іншої процедури Q, то для процедури Q відводиться область вільної пам'яті точно таким же чином, як і для процедури Р. При цьому змінні, описані в процедурі Р, та її проміжні результати залишаються в стеку і можуть бути знову використані, коли відбудеться повернення з процедури Q.

При виході з процедури Q необхідно відновити вміст базового регістра з номером N і передати управління на оператор, наступний за оператором виклику процедури Q. Для цього при вході в будь-яку процедуру на початку області вільної пам'яті відводиться 2 слова для сполучною інформації, що складається із спеціальних керуючих слів:
  • МКС — маркер стека аналогічний дескриптору, містить розмір області процедури, а його власний адреса — адреса початку цієї області. Зверху вниз:
    • 32 розряду — математичний адресу попереднього МКС («АДРЕСА»)
    • 1 розряд — ознака блокування запису адресної інформації («БЛ»)
    • 1 розряд — ознака привілейованої процедури («РЕЖ»)
    • 19 розрядів — розмір області пам'яті в стеку, відведеної для процедури («РОЗМІР»)
    • 2 розряду — тип повернення: без значення; 1 слово; 2 слова («ДТ»)
    • 5 розрядів — резерв
    • 4 розряду — номер лексикографічного рівня процедури («LL»)
  • УСВ — керуюче слово повернення — мітка, по якій буде передано управління після повернення з даної процедури плюс додаткова інформація
    • 18 розрядів — "Δ АДРЕСИ" різниця між адресою даного УСВ і адресою МКС запустила процедури
    • 3 розряду — «тригери», інформація про стан запустила процедури
    • … ознаки завершеності входу, переривання…
    • 16 розрядів — «Нк», номер байта повернення відносної програмної бази
    • 11 розрядів — «БАЗА», інформація про базу програмного сегмента, фактично, адреса функції, пара (Nk & БАЗА) містить адресу повернення
    • 4 розряду — номер лексикографічного рівня процедури
При виклику процедури Q прийнята наступна послідовність операцій:
  1. Завантажується в стек мітка процедури Q, тобто інформація про те, в якому місці математичної пам'яті знаходиться вхід в процедуру Q.
  2. Виконується команда «МС» — маркувати стек. При цьому:
    1. відводиться вільна комірка у верхівці стека, і в неї поміщається заготівля УСВ для викликається процедури Q
    2. в полі "Δ АДРЕСИ" заготовки УСВ записується різниця між адресу УСВ і вмістом УМЗ (допоміжний регістр, називаний покажчиком маркера стека), формується так звана динамічна ланцюжок. У УМЗ записується адреса мітки. Потрібен цей регістр лише в особливий період, при передачі параметрів.

    3. далі йдуть команди, які послідовно завантажують в стек інформацію про фактичні параметри.
  3. Виконується команда «ВХІД».
Вихід з процедури здійснюється командою «ПОВЕРНЕННЯ», при цьому:
  1. за лексикографическому рівнем процедури (До) знаходяться МКС і УСВ поточної процедури, фактично, базовий регістр зі зсувом 0 дивиться на МКС
  2. відповідне поле в УСВ процедури («ДТ») вказує, залишати значення в стеку
  3. за інформацією, що зберігається в полі "Δ АДРЕСИ" процедури (динамічна ланцюжок) поля «МАНАЧ» дескриптора До, знаходиться МКС процедури, що викликала дану (Р) і формується новий дескриптор в її базовому регістрі.
  4. управління передається в процедуру Р за інформацією, що зберігається в її УСВ. При цьому за вмістом поля «БАЗА» в регістр бази записується дескриптор програмного сегмента процедури Р, а в лічильник команд записується вміст поля Nk — номер програмного слова, з якого має бути відновлено виконання процедури Р.

АЛГОЛ60, ПЛ/1, СИМУЛА, АЛГОЛ68 мови з блочною структурою

Розробниками прийнята точка зору, згідно з якою блок є приватним випадком процедури без параметрів, у якій опис та виклик знаходяться в одному і тому ж місці програми.

Отже, кожен вхід в блок супроводжується внесенням на вершину стека двох слів — МКС & УСВ і імітацією виклику процедури. Оскільки параметрів немає, все робиться одразу.
Для блоку виділяється в стеку область під локальні змінні і створюється дескриптор під цю область плюс попередні 2 слова з контекстом.

Однак, блок відрізняється від повноцінної функції тим, що в ньому доступні змінні з вищестоящих блоків тієї ж функції. Як це забезпечити?

Дана проблема була вирішена організацією стека дескрипторів. Як ми пам'ятаємо, у нас в наявності 32 дескриптора, перші 3 з яких зарезервовані. Інші використовуються як стек. Тобто при вході в блок ми нарощуємо поточний лексикографічний рівень (для цього є регістр рівня), при виході з блоку, зменшуємо його. Отже, максимально можливий рівень вкладеності блоків — від 30 для простих смертних до 32 для операційної системи.

Тут виникають проблеми — нехай ми з блоку з рівнем вкладеності N функції Q викликаємо функцію P зі своїм деревом блоків. Якщо нічого не зробити, то дескриптори блоків (базових регістрах) будуть перезаписані і повернення з P відбудеться у непрацездатний стан.

Природне місце, куди можна зберегти вмісту базових регістрів — контексти блоків в стеку. Насправді навіть зберігати нічого не потрібно, вміст базових регістрів можна заново відтворити при поверненні:
  1. знаходиться адресу МКС попередньої функції. Для цього достатньо крокувати назад по списку МКС число разів, відповідне попереднього значення лексикографічного (регістра) рівня
  2. попереднє значення лексикографічного рівня можна взяти в полі «LL» найближчого МКС
  3. поле «БАЗА» всіх переглядаються УСВ має бути однаковим і вказувати на адресу потрібної функції, це ще один критерій зупинки проходу за списком МКС
  4. у процесі проходу за списком на підставі поточного МКС перезаписуємо дескриптор з номером поля LL
Підтримка блочних мов виглядає досить громіздкою. Крім того, механізм базових регістрів має двоїсту природу — з одного боку, це механізм розмежування операційної системи, бібліотек і кінцевих програм. А з іншого боку — стек дескрипторів блоків. Чому так вийшло?

Через апаратного захисту пам'яті. Кожному блоку відповідає свій дескриптор, стек блоків відповідає початковому дескрипторів.

Чому не можна було обійтися єдиним дескриптором для всієї функції? По всій видимості тому, що тоді при вході у функцію довелося б виділяти локальну пам'ять для найгіршого випадку.

Наприклад:
procedure p;
begin
integer i; 
...
begin
integer s array [1:1000];
...
end
...
end

навіть якщо внутрішній блок відвідується вкрай рідко, нам тим не менш довелося б щоразу при виклику P виділяти під локальні змінні пам'ять для 1001 цілого, а не 1 фактично необхідного. В умовах, коли пам'ять є досить дорогим ресурсом, це вагомий аргумент.

Але чому не зробити для функції дескриптор від МКС функції до кінця стека?
Тому що фактично це буде відмова від апаратного захисту пам'яті (на 50% :). Крім того, можливо, сегмент стека вмів (або планувалося, що буде вміти) розширюватися при переповненні, тоді у нього просто немає кінця.

Справедливості заради, на сучасних «Эльбрусах» реалізований (в цілях безпеки, мабуть) другий апаратний стек для зберігання інформації повернення [9]. За допомогою такого механізму вищеописана захист реалізується куди як більш елегантно. Правда, сучасні «Эльбрусы» мають мало спільного з предметом даної статті.

Разом
Ось ми і дісталися до кінця. При цьому розглянули основні архітектурні особливості Эльбрусов". Автор навмисно не стосувався деяких аспектів, наприклад, роботи з периферією, пристрої файлової системи… т. к. за минулий період все настільки сильно змінилося, що (з погляду автора) являє інтерес з майже археологічної точки зору — «цікаво, ну і як же вони викручувалися в той час?».

А ось, деякі саме архітектурні ідеї, закладені в комплекс, не втратили актуальності й донині. Наприклад, теговая система продовжує жити в «эльбрусах» сучасних. Шкода, що ідея суперскалярного стекового процесора не прижилася, хоча концептуально вона (на погляд автора, принаймні) стрункішою і простіше, ніж ідея реєстрової суперскалярної архітектури або архітектур з явним паралелізмом.

У будь-якому випадку, на момент своєї появи «Ельбрус» безсумнівно був на самому вістрі прогресу, причому його творці прокладали свій власний шлях у майбутнє, спираючись, звичайно, на світовий досвід, висували і реалізовували оригінальні ідеї.

Подальший розвиток комплексу його творці вбачали [2, стор 201] в тому числі і у використанні конвейеризированного векторного співпроцесора (який існував і на реальних задачах показував продуктивність у 80 Mflops [2, с 166]), що цілком вкладається в наші сьогоднішні уявлення.

Джерела
[1] Масич Р. Ф. лекції
[2] Збірник статей Ст. З Бурцева (керівник проекту, головний з апаратної частини)
[3] WIKI-стаття про Эльбрусы
[4] www.osp.ru/os/2009/02/7314081
[5] www.ixbt.com/cpu/e2k-spec.html
[6] тут інтерес представляють коментарі Погорилого
[7] www.pvsm.ru/programmirovanie/106969
[8] www.caesarion.ru/warrax/w/warrax.net/88/elbrus.html
[9] www.mcst.ru/files/511cea/886487/1a8f40/000000/book_elbrus.pdf
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.