Побудуйте робочу гру в тетріс у грі життя Конвей


993

Ось теоретичне запитання - те, що не дає легкої відповіді ні в якому разі, навіть не тривіального.

У грі життя Конвея існують такі конструкції, як метапіксель, які дозволяють Game of Life також імітувати будь-яку іншу систему правил Game-of-Life. Крім того, відомо, що Гра життя є Тюрінг-завершеною.

Ваше завдання - побудувати стільниковий автомат, використовуючи правила життєвої гри Конвей, які дозволять грати в гру в тетріс.

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

Ваша програма оцінюватиметься за такими параметрами (з нижчими критеріями, які виконують роль краватки для вищих критеріїв):

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

  • Менші зміни вхідних даних - найменше комірок (для найгіршого випадку у вашому автоматі), які потрібно вручну налаштувати для перерви.

  • Швидке виконання - виграє найменше покоління, яке просунуло одну галочку в симуляції.

  • Початкове число живих клітин - менший рахунок виграє.

  • Перший до повідомлення - виграє попередня публікація.


95
Чи означає "демонстративно працюючий приклад" щось, що працює протягом кількох годин, або щось, що може бути доведено правильним, навіть якщо це займе, поки не зіграє теплова смерть Всесвіту?
Пітер Тейлор

34
Я впевнений, що щось подібне можливо і грає. Просто небагато людей мають досвід, щоб можна було запрограмувати те, що, мабуть, є однією з езотеричних "мов асемблери" у світі.
Джастін Л.

58
Над цим викликом працює над! Чат | Прогрес | Блог
mbomb007

49
Станом на 5:10 сьогодні вранці (9:10 UTC), це питання є першим питанням в історії PPCG, яке набрало 100 голосів, не отримавши відповіді! Молодці всі.
Джо Z.

76
Я намагаюся вирішити це ... Тепер, коли я лягаю спати, я всюди бачу планери, що стикаються в гігантському безладі. Мій сон повний кошмарів, коли пульсуючі пентадекатлони перегороджують мені шлях, і Гершелі розвиваються, щоб поглинути мене. Будь ласка, Джон Конвей, моліться за мене ...
дим

Відповіді:


937

Це почалося як квест, але закінчилося як одісея.

Квест для процесора Tetris, 2 940 928 x 10 295 296

Файл шаблону, у всій своїй красі, можна знайти тут , для перегляду в браузері тут .

Цей проект є кульмінацією зусиль багатьох користувачів протягом останніх 1 та 1/2 років. Незважаючи на те, що склад команди з часом змінювався, учасники написали наступне:

Ми також хотіли б подякувати 7H3_H4CK3R, Conor O'Brien та багатьом іншим користувачам, які доклали зусиль для вирішення цієї проблеми.

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

Будь ласка, поширюйте будь-які надбавки або принади між усіма членами команди.

Зміст

  1. Огляд
  2. Метапікселі та VarLife
  3. Обладнання
  4. QFTASM та Cogol
  5. Асамблея, переклад та майбутнє
  6. Нова мова та укладач

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


Частина 1: Огляд

Основна ідея цього проекту - абстракція . Замість того, щоб розвивати гру «Тетріс» у «Житті» безпосередньо, ми поступово піднімали абстракцію декількома кроками. На кожному шарі ми віддаляємося від труднощів Життя та наближаємось до побудови комп'ютера, який легко програмувати, як будь-який інший.

По-перше, ми використовували метапікселі OTCA як основу нашого комп'ютера. Ці метапікселі здатні наслідувати будь-яке "подібне до життя" правило. Wireworld та комп'ютер Wireworld послужили важливими джерелами натхнення для цього проекту, тому ми прагнули створити аналогічну конструкцію з метапікселями. Хоча неможливо емуляцію Wireworld за допомогою метапікселів OTCA, можна призначати різні метапікселі різними правилами та створювати компонування метапікселів, які функціонують аналогічно дротам.

Наступним кроком було побудова різноманітних фундаментальних логічних воріт, які послужать основою для комп'ютера. Вже на цьому етапі ми маємо справу з концепціями, подібними до дизайну процесорів у реальному світі. Ось приклад АБО ворота, кожна комірка на цьому зображенні насправді є цілим метапікселем OTCA. Ви можете бачити, як "електрони" (кожен представляє по одному біту даних) входять і виходять з воріт. Ви також можете побачити всі різні типи метапікселів, які ми використовували на нашому комп'ютері: B / S як чорний фон, B1 / S синім, B2 / S зеленим та B12 / S1 червоним.

зображення

Звідси ми розробили архітектуру для нашого процесора. Ми витратили значні зусилля на розробку архітектури, яка була максимально неезотеричною і максимально легко реалізованою. Тоді як комп'ютер Wireworld використовував рудиментарну архітектуру, викликану транспортом, цей проект використовує набагато більш гнучку архітектуру RISC, що комплектується декількома опкодами та режимами адреси. Ми створили мову складання, відому як QFTASM (Quest for Tetris Assembly), яка керувала побудовою нашого процесора.

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

Ось ілюстрація нашої архітектури процесорів:

зображення

Звідси просто питання впровадження тетрісу на комп’ютері. Щоб допомогти досягти цього, ми працювали над кількома методами компіляції мови вищого рівня до QFTASM. У нас є основна мова під назвою Cogol, друга, більш розвинена мова, що розробляється, і нарешті у нас є недобудований GCC. Поточна програма Tetris була написана в / складений з Cogol.

Як тільки генерується остаточний код тетра QFTASM, останніми кроками було збирання цього коду до відповідного ПЗУ, а потім від метапікселів до основної гри Життя, що завершує нашу побудову.

Запуск тетрісу

Для тих, хто хоче грати в тетріс, не возившись з комп'ютером, ви можете запустити вихідний код Tetris на інтерпретаторі QFTASM . Встановіть відображувані адреси ОЗУ на 3-32, щоб переглянути всю гру. Ось постійна посилання для зручності: Tetris в QFTASM .

Особливості гри:

  • Всі 7 тетроміно
  • Рух, обертання, м'які краплі
  • Лінія очищає та забиває
  • Попередній перегляд
  • Гравець вводить випадковість

Дисплей

Наш комп'ютер представляє плату Tetris як сітку в межах своєї пам'яті. Адреси 10-31 відображають дошку, адреси 5-8 відображають деталь попереднього перегляду, а адреса 3 містить бал.

Вхідні дані

Введення в гру виконується шляхом ручного редагування вмісту RAM-адреси 1. Використовуючи інтерпретатор QFTASM, це означає, що виконайте пряме записування на адресу 1. Шукайте "Пряме записування в ОЗУ" на сторінці перекладача. Кожен хід вимагає редагування лише одного біту оперативної пам’яті, і цей регістр вводу автоматично очищається після зчитування події введення.

value     motion
   1      counterclockwise rotation
   2      left
   4      down (soft drop)
   8      right
  16      clockwise rotation

Система підрахунку балів

Ви отримуєте бонус за очищення декількох ліній за один виток.

1 row    =  1 point
2 rows   =  2 points
3 rows   =  4 points
4 rows   =  8 points

14
@ Christopher2EZ4RTZ У цьому оглядовому дописі детально описується робота багатьох учасників проекту (включаючи фактичне написання оглядового повідомлення). Як такий, доцільно, щоб він був CW. Ми також намагалися уникнути того, щоб одна людина мала дві посади, оскільки це призвело б до отримання несправедливої ​​кількості представників, оскільки ми намагаємось утримати реп.
Mego

28
Перш за все +1, тому що це шалено дивовижне досягнення (тим більше, що ви побудували комп’ютер у грі життя, а не просто тетріс). По-друге, наскільки швидко працює комп'ютер і наскільки швидка гра в тетріс? Це навіть віддалено грати? (знову: це приголомшливо)
Сократичний Фенікс

18
Це ... це абсолютно божевільно. Поставте +1 до всіх відповідей відразу.
scottinet

28
Попередження для тих, хто хоче розподілити невеликі щедроти за відповіді: ви повинні подвоювати суму своєї винагороди кожен раз (поки ви не наберете 500), тому одна людина не може дати однакову суму за кожну відповідь, якщо ця сума не становить 500 повторень.
Мартін Ендер

23
Це єдина найбільша річ, яку я коли-небудь прокручував, розуміючи дуже мало.
Інженер Тост

678

Частина 2: OTCA Metapixel і VarLife

OTCA Metapixel

Метапіксель OTCA
( Джерело )

OTCA Metapixel являє собою конструкцію у грі Конвея життя , які можуть бути використані для моделювання будь-якого життя, як клітинні автомати. Як говорить LifeWiki (посилання вище),

Метапіксель OTCA - це осередок 35328 одиниць 35328 періоду 2048 × 2048, який був сконструйований Brice Due ... Він має багато переваг ... включаючи можливість емуляції будь-якого життєвого мобільного автомата та факт, що при зменшенні масштабу вмикається і ВИКЛ. комірки легко відрізнити ...

Що тут означає життєві клітинні автомати, це по суті те, що клітини народжуються, а клітини виживають відповідно до того, скільки живуть їх вісім сусідніх клітин. Синтаксис цих правил такий: B, за яким слідують числа живих сусідів, які спричинить народження, потім коса риса, потім S, а потім кількість живих сусідів, які підтримуватимуть клітку живою. Трохи багатослівно, тому, думаю, приклад допоможе. Канонічна Гра життя може бути представлена ​​правилом B3 / S23, яке говорить, що будь-яка мертва клітина з трьома живими сусідами стане живою, а будь-яка жива клітина з двома-трьома живими сусідами залишиться живою. В іншому випадку клітина гине.

Незважаючи на те, що це клітинка 2048 x 2048, метапіксель OTCA насправді має обмежувальну коробку з 2058 x 2058 комірок, тому причина, що вона перекривається на п’ять комірок у кожному напрямку зі своїми сусідами по діагоналі . Осередки, що перекриваються, служать для перехоплення планерів - які випромінюються для того, щоб сигналізувати сусідам метаелементів, що він увімкнений, - щоб вони не заважали іншим метапікселям і не відлітали на невизначений час. Правила народження та виживання кодуються в спеціальній секції клітин з лівої сторони метапікселя за наявністю або відсутністю їдців у визначених положеннях уздовж двох стовпців (один для народження, другий для виживання). Щодо виявлення стану сусідніх комірок, ось як це відбувається:

Потім потік 9-LWSS проходить за годинниковою стрілкою навколо клітини, втрачаючи LWSS для кожної сусідньої комірки "на", яка викликала реакцію медоносу. Кількість відсутніх LWSS підраховується шляхом виявлення положення переднього LWSS шляхом врізання в нього іншого LWSS з протилежного напрямку. Це зіткнення вивільняє планери, що викликає ще одну-дві реакції медоносу, якщо їдці, що свідчать про відсутність стану народження / виживання.

Більш детальну схему кожного аспекту метапікселя OTCA можна знайти на його оригінальному веб-сайті: Як це працює? .

VarLife

Я побудував онлайн-симулятор життєлюбних правил, де можна змусити будь-яку клітину вести себе відповідно до будь-якого життєвого правила і назвав її "Варіації життя". Це ім’я було скорочено до "VarLife", щоб бути більш стислим. Ось знімок екрана (посилання на нього тут: http://play.starmaninnovations.com/varlife/BeeHkfCpNR ):

Скріншот VarLife

Помітні функції:

  • Переміщуйте клітини між живими / мертвими та фарбуйте дошку за різними правилами.
  • Можливість запускати та зупиняти моделювання та робити один крок за один раз. Також можна зробити певну кількість кроків якомога швидше або повільніше, зі швидкістю, встановленою в полях "тик за секунду" і "мілісекунди за клік".
  • Очистіть усі живі клітини або повністю поверніть плату до порожнього стану.
  • Можна змінювати розміри комірок та плати, а також включати тороїдальне обгортання по горизонталі та / або вертикалі.
  • Постійні посилання (які кодують всю інформацію в URL-адресі) та короткі URL-адреси (адже іноді просто занадто багато інформації, але вони все одно приємні).
  • Набори правил із специфікацією B / S, кольорами та необов’язковістю випадковості.
  • І останнє, але, безумовно, не менш важливе, надання gif!

Функція візуалізації для gif - моя улюблена як тому, що на реалізацію пішло багато робіт, тому це було справді задоволення, коли я нарешті зламав її о 7 ранку, і тому, що дуже легко ділитися конструкціями VarLife з іншими .

Основні схеми VarLife

Загалом, комп'ютеру VarLife потрібні лише чотири типи комірок! Вісім штатів у всіх підрахунках мертвих / живих станів. Вони є:

  • B / S (чорно-білий), який служить буфером між усіма компонентами, оскільки клітини B / S ніколи не можуть бути живими.
  • B1 / S (синій / блакитний), який є основним типом комірок, що використовується для поширення сигналів.
  • B2 / S (зелений / жовтий), який в основному використовується для управління сигналом, гарантуючи, що він не розповсюджується.
  • B12 / S1 (червоний / оранжевий), який використовується в декількох спеціалізованих ситуаціях, таких як схрещування сигналів і зберігання небагато даних.

Використовуйте цю коротку URL-адресу, щоб відкрити VarLife з цими правилами, які вже закодовані: http://play.starmaninnovations.com/varlife/BeeHkfCpNR .

Провід

Існує кілька різних конструкцій дроту з різними характеристиками.

Це найпростіший і найпростіший провід у VarLife, смужка синього кольору, облямована смужками зеленого кольору.

основний провід
Коротка URL-адреса: http://play.starmaninnovations.com/varlife/WcsGmjLiBF

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

односпрямований провід
Коротка URL-адреса: http://play.starmaninnovations.com/varlife/ARWgUgPTEJ

Діагональні дроти також існують, але вони зовсім не використовуються.

діагональний дріт
Коротка URL-адреса: http://play.starmaninnovations.com/varlife/kJotsdSXIj

Ворота

Насправді існує безліч способів побудувати кожен окремий хвірт, тому я покажу лише один приклад кожного виду. Цей перший gif демонструє ворота AND, XOR та OR відповідно. Основна ідея тут полягає в тому, що зелена клітина діє як AND, синя клітина діє як XOR, а червона клітина діє як АБО, а всі інші клітини навколо них просто там, щоб правильно контролювати потік.

І, логічна брама І, XOR, АБО
Коротка URL-адреса: http://play.starmaninnovations.com/varlife/EGTlKktmeI

Ворота AND-NOT, скорочено до "ANT gate", виявились життєво важливою складовою. Це ворота, які передають сигнал від A, якщо і тільки тоді, коли немає сигналу від B. Отже, "A AND NOT B".

І-НЕ ворота
Коротка URL-адреса: http://play.starmaninnovations.com/varlife/RsZBiNqIUy

Хоча це не зовсім ворота , провідна плитка все ще дуже важлива і корисна.

дротовий перетин
Коротка URL-адреса: http://play.starmaninnovations.com/varlife/OXMsPyaNTC

Між іншим, тут НЕ ворота. Це тому, що без вхідного сигналу повинен бути вироблений постійний вихід, який не дуже добре працює з різноманітністю в термінах, необхідних для поточного комп'ютерного обладнання. Ми все одно просто чудово обійшлися без цього.

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

Щоб побачити більше воріт, які були виявлені / побудовані в процесі вивчення компонентів схеми, перегляньте це повідомлення в блозі від PhiNotPi: Building Blocks: Logic Gates .

Компоненти затримки

У процесі проектування апаратних засобів комп’ютера, KZhang розробив багаторазові компоненти затримки, показані нижче.

Затримка з 4 галочками: Коротка URL-адреса: http://play.starmaninnovations.com/varlife/gebOMIXxdh
Затримка 4 галочок

5-титична затримка: Коротка URL-адреса: http://play.starmaninnovations.com/varlife/JItNjJvnUB
5 тикових затримок

8-титична затримка (три різні точки входу): Коротка URL-адреса: http://play.starmaninnovations.com/varlife/nSTRaVEDvA
8 тикових затримок

11-титична затримка: Коротка URL-адреса: http://play.starmaninnovations.com/varlife/kfoADussXA
11 тик затримки

12-титична затримка: Коротка URL-адреса: http://play.starmaninnovations.com/varlife/bkamAfUfud
12 тикових затримок

14-титична затримка: Коротка URL-адреса: http://play.starmaninnovations.com/varlife/TkwzYIBWln
14 тик затримки

Затримка 15 галочок (перевірено порівнянням з цим ): Коротка URL-адреса: http://play.starmaninnovations.com/varlife/jmgpehYlpT
15 тикових затримок

Ну, це все для основних компонентів мікросхем у VarLife! Дивіться апаратний пост KZhang про основні схеми комп'ютера!


4
VarLife - одна з найбільш вражаючих частин цього проекту; його універсальність і простота порівняно, наприклад, з Wireworld є феноменальним. Мета-піксель OTCA здається набагато більшим, ніж потрібно, чи були спроби гольфу?
примо

@primo: Дейв Грін начебто працює над цим, схоже. chat.stackexchange.com/transcript/message/40106098#40106098
El'endia Starman

6
Так, досягли пристойного прогресу в ці вихідні на серці металевої 512x512 HashLife ( conwaylife.com/forums/viewtopic.php?f=&p=51287#p51287 ). Метаелемент може бути дещо меншим, залежно від того, якою великою площею "пікселя" потрібно сигналізувати про стан комірки, коли ви зменшуєте масштаб. Очевидно, здається, варто зупинитися на точній плитці розміром 2 ^ N, оскільки алгоритм HashLife Голлі зможе запустити комп'ютер набагато швидше.
Дейв Грін

2
Чи не можна проводки та ворота реалізовувати менш "марно"? Електрон був би представлений планером або космічним кораблем (залежно від напрямку). Я бачив домовленості, які переспрямовують їх (а при необхідності змінюють один на інший) та деякі ворота, що працюють із планерами. Так, вони займають більше місця, дизайн складніший і терміни повинні бути точними. Але як тільки у вас є ці основні будівельні блоки, вони повинні бути досить простими для складання, і вони займуть набагато менше місця, ніж VarLife, реалізований за допомогою OTCA. Він також біг швидше.
Хеймдалл

@Heimdall Хоча це спрацювало б добре, він би не показав себе добре під час гри в тетріс.
MilkyWay90

649

Частина 3: Обладнання

Маючи наші знання про логічні ворота та загальну структуру процесора, ми можемо почати проектувати всі компоненти комп'ютера.

Демультиплексор

Демультиплексор або демукс є ключовою складовою для ПЗУ, ОЗУ та АЛУ. Він спрямовує вхідний сигнал до одного з безлічі вихідних сигналів на основі деяких даних селектора. Він складається з 3-х основних частин: послідовного до паралельного перетворювача, перевірки сигналу та розгалужувача тактового сигналу.

Почнемо з перетворення даних послідовного селектора в "паралельні". Це робиться шляхом стратегічного розбиття та затримки даних, щоб крайній лівий біт даних перетинав тактовий сигнал у лівій лівій площі 11х11, наступний біт даних перетинає тактовий сигнал на наступному квадраті 11х11 тощо. Хоча кожен біт даних буде виводитися на кожні 11x11 квадратів, кожен біт даних буде перетинатися з тактовим сигналом лише один раз.

Послідовний паралельний перетворювач

Далі ми перевіримо, чи паралельні дані відповідають заданій адресі. Ми робимо це, використовуючи ворота AND і ANT на тактових та паралельних даних. Однак нам потрібно переконатися, що паралельні дані також виводяться, щоб їх можна було знову порівняти. Це ворота, які я придумав:

Ворота перевірки сигналу

Нарешті, ми просто розділили тактовий сигнал, складемо купу шашок сигналу (по одному на кожну адресу / вихід) і у нас є мультиплексор!

Мультиплексор

ПЗУ

ПЗУ повинен приймати адресу як вхід і надсилати інструкцію за цією адресою як вихід. Почнемо з використання мультиплексора для спрямування сигналу годинника на одну з інструкцій. Далі нам потрібно генерувати сигнал за допомогою деяких дротяних перетинів та АБО воріт. Провідні перехрестя дають можливість синхросигналу просунути всі 58 біт інструкції, а також дозволяють згенерованому сигналу (в даний час паралельно) рухатися вниз через ПЗУ для виведення.

ROM біти

Далі нам просто потрібно перетворити паралельний сигнал на послідовні дані, і ПЗУ завершено.

Паралельно послідовного перетворювача

ПЗУ

ПЗУ наразі генерується за допомогою запуску сценарію в Golly, який переведе код складання з буфера обміну в ROM.

SRL, SL, SRA

Ці три логічні ворота використовуються для зсуву бітів, і вони складніші, ніж ваш типовий AND, АБО, XOR і т.д. в даних. Другий аргумент, що надається цим воротам, диктує, скільки бітів потрібно змінити.

Для SL та SRL нам потрібно

  1. Переконайтесь, що 12 найбільш значущих бітів не включені (інакше вихід просто 0), і
  2. Затримка даних правильна кількість на основі 4 найменш значущих біт.

Це можливо з купою воріт AND / ANT та мультиплексором.

SRL

SRA дещо відрізняється, тому що нам потрібно скопіювати біт знаку під час зміни. Ми робимо це за допомогою ANDing тактового сигналу з бітовим знаком, а потім копіюємо, що виводить купу разів за допомогою дротяних розгалужувачів та АБО затворів.

SRA

Засувка встановлення-скидання (SR)

Багато частин функціоналу процесора покладаються на можливість зберігання даних. Використовуючи 2 червоні клітини B12 / S1, ми можемо зробити саме це. Дві клітини можуть підтримувати один одного, а також можуть залишатися разом. За допомогою додаткового набору, скидання та зчитування схеми ми можемо зробити просту засувку SR.

SR засувка

Синхронізатор

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

Синхронізатор

Прочитайте лічильник

Цей пристрій відстежує, скільки ще разів йому потрібно звертатися з оперативної пам’яті. Це робиться за допомогою пристрою, подібного до засувки SR: T триггер. Кожен раз, коли T триггер отримує вхід, він змінює стан: якщо він був увімкненим, він вимикається і навпаки. Коли T-фліп-флоп перевертається з увімкненого та вимкненого, він посилає вихідний імпульс, який може подаватись в інший трип-флоп, утворюючи 2-бітний лічильник.

Двобітний лічильник

Для того, щоб зробити лічильник читання, нам потрібно встановити лічильник у відповідному режимі адресації з двома воротами ANT і за допомогою вихідного сигналу лічильника вирішити, куди слід направляти тактовий сигнал: на АЛУ або на ОЗУ.

Прочитайте лічильник

Прочитайте чергу

Черга читання повинна відслідковувати, який лічильник читання надсилає вхід в оперативну пам'ять, щоб він міг відправити вихід RAM в правильне місце. Для цього ми використовуємо кілька фіксаторів SR: одна засувка на кожен вхід. Коли сигнал передається в ОЗУ з лічильника зчитування, сигнал тактового сигналу розбивається і встановлює SR фіксатора лічильника. Потім на виході оперативної пам’яті наводиться ANDed за допомогою засувки SR, а тактовий сигнал з оперативної пам’яті скидає засувку SR.

Прочитайте чергу

АЛУ

ALU функціонує аналогічно черзі читання, оскільки використовує засувку SR для відстеження, куди надсилати сигнал. По-перше, засувка SR логічної схеми, що відповідає опкоду інструкції, встановлюється за допомогою мультиплексора. Далі, значення першого та другого аргументу ANDed за допомогою засувки SR, а потім передаються до логічних схем. Сигнал годинника скидає засувку під час проходження, щоб ALU можна було використовувати знову. (Більша частина мікросхеми заграна в гольф, і тонна керування затримкою забита, так що це виглядає як трохи безлад)

АЛУ

ОЗП

Оперативна пам’ять була найскладнішою частиною цього проекту. Це вимагало дуже специфічного контролю над кожною засувкою SR, яка зберігала дані. Для читання адреса надсилається в мультиплексор і надсилається одиницям оперативної пам'яті. Одиниці оперативної пам’яті виводять паралельно збережені ними дані, які перетворюються на послідовні та виводяться. Для запису адреса надсилається в інший мультиплексор, дані, що записуються, перетворюються з послідовного в паралельний, а блоки ОЗУ поширюють сигнал по всій ОЗУ.

Кожен RAM-модуль розміром 22x22 має таку основну структуру:

Блок оперативної пам'яті

Збираючи всю оперативну пам’ять разом, ми отримуємо щось таке:

ОЗП

Збираючи все разом

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

Завантаження: - Готовий комп'ютер Tetris - сценарій створення ПЗУ, порожній комп'ютер та основний комп'ютер

Комп'ютер


49
Я просто хотів би сказати, що образи в цій публікації з будь-якої причини дуже красиві на мою думку. : P +1
HyperNeutrino

7
Це найдивовижніше, що я коли-небудь бачив .... Я б +20, якби міг
FantaC

3
@tfbninja Ви можете, це називається щедротою, і ви можете дати 200 репутації.
Фабіан Рьолінг

10
Чи вразливий цей процесор для нападу Spectre та Meltdown? :)
Ferrybig

5
@Ferrybig не передбачає гілок, тому я сумніваюся.
JAD

621

Частина 4: QFTASM та Cogol

Огляд архітектури

Словом, наш комп'ютер має 16-бітну асинхронну архітектуру RISC Гарварду. Створюючи процесор вручну, архітектура RISC ( скорочений набір інструкцій ) є практично необхідною умовою. У нашому випадку це означає, що кількість опкодів невелика і, що набагато важливіше, що всі інструкції обробляються дуже схожим чином.

Для довідки комп'ютер Wireworld використовував транспортно-спрацьовану архітектуру , в якій єдиною інструкцією було проведено MOVобчислення, записавши / прочитавши спеціальні регістри. Хоча ця парадигма призводить до дуже простої у виконанні архітектури, результат також є непридатним для кордону: всі арифметичні / логічні / умовні операції потребують трьох інструкцій. Нам було зрозуміло, що ми хочемо створити набагато менш езотеричну архітектуру.

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

  • Немає реєстрів. Кожна адреса оперативної пам'яті трактується однаково і може бути використана як будь-який аргумент для будь-якої операції. У певному сенсі це означає, що вся оперативна пам'ять може оброблятися як регістри. Це означає, що немає спеціальних інструкцій щодо завантаження / зберігання.
  • У подібному руслі відображення пам'яті. Все, що можна було написати або прочитати з спільної схеми адресації. Це означає, що лічильник програм (ПК) - це адреса 0, і єдина відмінність між звичайними інструкціями та інструкціями контрольного потоку полягає в тому, що в інструкціях контрольного потоку використовується адреса 0.
  • Дані є послідовними в передачі, паралельно зберігаються. Завдяки нашому комп'ютеру, що базується на електронах, додавання та віднімання значно легше здійснити, коли дані передаються у серійному мало-ендіанському (найменш значущому біті спочатку). Крім того, серійні дані знімають потребу в громіздких шинах даних, які дійсно широкі і громіздкі за часом належним чином (для того, щоб дані залишалися разом, усі "смуги" шини повинні мати однакові затримки в дорозі).
  • Гарвардська архітектура, що означає поділ між пам'яттю програми (ROM) та пам'яттю даних (RAM). Хоча це і знижує гнучкість процесора, це допомагає оптимізувати розміри: довжина програми набагато більша за обсяг оперативної пам’яті, який нам знадобиться, тому ми можемо розділити програму на ПЗУ, а потім зосередитись на стисканні ПЗУ , що набагато простіше, коли він доступний лише для читання.
  • 16-бітна ширина даних. Це найменша потужність з двох, яка ширша від стандартної дошки Tetris (10 блоків). Це дає нам діапазон даних від -32768 до +32767 та максимальну довжину програми - 65536 інструкцій. (2 ^ 8 = 256 інструкцій вистачає для більшості простих речей, які ми можемо хотіти робити процесором іграшок, але не Tetris.)
  • Асинхронний дизайн. Замість того, щоб мати центральний годинник (або, що еквівалентно, кілька годин), що диктує час роботи комп'ютера, усі дані супроводжуються "синхросигналом", який рухається паралельно з даними, коли він обтікає комп'ютер. Окремі шляхи можуть бути коротшими, ніж інші, і хоча це створює труднощі для централізованої конструкції, асинхронна конструкція може легко справлятися з операціями зі змінним часом.
  • Всі інструкції мають однаковий розмір. Ми вважали, що архітектура, в якій кожна інструкція має 1 опкод з 3 операндами (значення значення призначення), була найбільш гнучким варіантом. Це охоплює операції з бінарними даними, а також умовні ходи.
  • Проста система режиму адресації. Наявність різноманітних режимів адресації дуже корисно для підтримки таких речей, як масиви чи рекурсія. Нам вдалося реалізувати кілька важливих режимів адресації за допомогою відносно простої системи.

Ілюстрація нашої архітектури міститься в оглядовому пості.

Функціональність та операції ALU

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

Умовні рухи

Умовні ходи є дуже важливими і служать як дрібномасштабним, так і великомасштабним контрольним потоком. "Маломасштабний" відноситься до його здатності контролювати виконання певного руху даних, тоді як "великомасштабний" відноситься до його використання як умовної операції стрибка для передачі потоку управління на будь-який довільний фрагмент коду. Немає спеціальних операцій стрибків, оскільки, завдяки картографуванню пам'яті, умовний хід може як скопіювати дані в звичайну оперативну пам'ять, так і скопіювати адресу призначення на ПК. Ми також вирішили відмовитись як від безумовних рухів, так і від безумовних стрибків з аналогічної причини: обидва можуть бути реалізовані як умовний хід з умовою, важко кодованою для ІСТИНИ.

Ми вибрали два різних типи умовних рухів: "перемістити, якщо не нуль" ( MNZ) і "перемістити, якщо менше нуля" ( MLZ). Функціонально - MNZце перевірка того, чи є будь-який біт у даних 1, в той час як MLZзводиться до перевірки, чи є бітовий знак 1. Вони корисні для рівностей і порівнянь відповідно. Причиною того, як ми обрали цих двох над іншими, такими як "рухатись, якщо нуль" ( MEZ) або "рухатись, якщо більший за нуль" ( MGZ), було те, MEZщо вимагатиме створення ІСТИЧНОГО сигналу з порожнього сигналу, в той час MGZяк це більш складна перевірка, що вимагає знак біта буде 0, хоча принаймні один інший біт буде 1.

Арифметика

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

Ми вирішили використати подання доповнення 2 для від'ємних чисел, оскільки це робить додавання і віднімання більш послідовними. Варто зазначити, що комп'ютер Wireworld використовував додаток 1.

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

Побітові операції

Наш процесор має AND, ORі XORінструкції , які робити те , що можна було б очікувати. Замість того, щоб мати NOTінструкцію, ми вибрали інструкцію "і-не" ( ANT). Складність з NOTінструкцією знову ж таки полягає в тому, що він повинен створювати сигнал від нестачі сигналу, що складно з стільниковими автоматами. ANTІнструкція повертає 1 , тільки якщо перший аргумент біт дорівнює 1 , а другий аргумент біт дорівнює 0. Таким чином, NOT xеквівалентно ANT -1 x(а також XOR -1 x). Крім того, ANTвін універсальний і має свою головну перевагу в маскуванні: у випадку програми Tetris ми використовуємо його для стирання тетроміно.

Зсув бітів

Операції з переміщенням бітів - це найскладніші операції, якими управляє АЛУ. Вони беруть два введення даних: значення для зсуву та кількість для його зміщення. Незважаючи на їх складність (через мінливу кількість зрушень), ці операції мають вирішальне значення для багатьох важливих завдань, включаючи безліч "графічних" операцій, що беруть участь у тетрісі. Зсуви бітів також послужать основою для ефективних алгоритмів множення / ділення.

У нашому процесорі є три операції зсуву бітів: "зрушення вліво" ( SL), "логічний зсув праворуч" ( SRL) і "арифметика зсуву вправо" ( SRA). Перші два бітові зсуви ( SLі SRL) заповнюють нові біти усіма нулями (це означає, що від'ємне число, зміщене вправо, більше не буде від'ємним). Якщо другий аргумент зрушення знаходиться поза діапазоном від 0 до 15, результатом є всі нулі, як ви могли очікувати. Для останнього бітового зсуву, SRAбітовий зсув зберігає знак введення, і тому діє як справжній поділ на два.

Інструкція Трубопровід

Зараз час поговорити про деякі суворі деталі архітектури. Кожен цикл процесора складається з наступних п'яти кроків:

1. Отримайте поточну інструкцію з ПЗУ

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

Опкод - це 4 біти для підтримки 16 унікальних опкодів, з яких 11 призначено:

0000  MNZ    Move if Not Zero
0001  MLZ    Move if Less than Zero
0010  ADD    ADDition
0011  SUB    SUBtraction
0100  AND    bitwise AND
0101  OR     bitwise OR
0110  XOR    bitwise eXclusive OR
0111  ANT    bitwise And-NoT
1000  SL     Shift Left
1001  SRL    Shift Right Logical
1010  SRA    Shift Right Arithmetic
1011  unassigned
1100  unassigned
1101  unassigned
1110  unassigned
1111  unassigned

2. Запишіть результат (при необхідності) попередньої інструкції в ОЗУ

Залежно від умови попередньої інструкції (наприклад, значення першого аргументу для умовного переміщення) виконується запис. Адреса запису визначається третім операндом попередньої інструкції.

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

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

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

3. Прочитайте дані аргументів поточної інструкції з оперативної пам'яті

Як було сказано раніше, кожен з трьох операндів складається як з даних даних, так і з режиму адресації. Слово даних - 16 біт, така ж ширина, як оперативна пам'ять. Режим адресації - 2 біти.

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

Ми прагнули уніфікувати концепції використання твердо кодованих чисел як операндів та використання адрес даних як операндів. Це призвело до створення режимів адресації на основі лічильника: режим адресації операнда - це просто число, що представляє, скільки разів дані повинні бути надіслані навколо циклу зчитування ОЗУ. Це охоплює негайне, пряме, опосередковане та подвійне непряме звернення.

00  Immediate:  A hard-coded value. (no RAM reads)
01  Direct:  Read data from this RAM address. (one RAM read)
10  Indirect:  Read data from the address given at this address. (two RAM reads)
11  Double-indirect: Read data from the address given at the address given by this address. (three RAM reads)

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

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

4. Обчисліть результат

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

Опкод і перший операнд використовуються для обчислення умови, яка визначає, записувати результат у пам'ять чи ні. У випадку умовних ходів це означає або визначення того, чи є якийсь біт в операнді 1 (для MNZ), або визначення, чи є бітовий знак 1 (для MLZ). Якщо опкод не є умовним переміщенням, то запис завжди виконується (умова завжди вірна).

5. Збільшення лічильника програми

Нарешті, лічильник програм зчитується, збільшується та записується.

Через позицію приросту ПК на ПК між записом читання та записом інструкції, це означає, що інструкція, що збільшує ПК на 1, є неоперативною. Інструкція, що копіює ПК на себе, призводить до того, що наступна інструкція буде виконуватися двічі поспіль. Але будьте попереджені, кілька інструкцій на ПК підряд можуть спричинити складні ефекти, включаючи нескінченне циклічне циклічне завершення, якщо ви не звернете уваги на інструкцію.

Квест Тетріс Асамблеї

Ми створили нову мову збірки під назвою QFTASM для нашого процесора. Ця мова збірки відповідає 1-до-1 машинному коду в ПЗУ комп'ютера.

Будь-яка програма QFTASM пишеться у вигляді серії інструкцій, по одній на рядок. Кожен рядок відформатований так:

[line numbering] [opcode] [arg1] [arg2] [arg3]; [optional comment]

Список опкодів

Як було обговорено раніше, існує одинадцять опкодів, підтримуваних комп'ютером, кожен з яких має три операнди:

MNZ [test] [value] [dest]  – Move if Not Zero; sets [dest] to [value] if [test] is not zero.
MLZ [test] [value] [dest]  – Move if Less than Zero; sets [dest] to [value] if [test] is less than zero.
ADD [val1] [val2] [dest]   – ADDition; store [val1] + [val2] in [dest].
SUB [val1] [val2] [dest]   – SUBtraction; store [val1] - [val2] in [dest].
AND [val1] [val2] [dest]   – bitwise AND; store [val1] & [val2] in [dest].
OR [val1] [val2] [dest]    – bitwise OR; store [val1] | [val2] in [dest].
XOR [val1] [val2] [dest]   – bitwise XOR; store [val1] ^ [val2] in [dest].
ANT [val1] [val2] [dest]   – bitwise And-NoT; store [val1] & (![val2]) in [dest].
SL [val1] [val2] [dest]    – Shift Left; store [val1] << [val2] in [dest].
SRL [val1] [val2] [dest]   – Shift Right Logical; store [val1] >>> [val2] in [dest]. Doesn't preserve sign.
SRA [val1] [val2] [dest]   – Shift Right Arithmetic; store [val1] >> [val2] in [dest], while preserving sign.

Режими адресації

Кожен з операндів містить як значення даних, так і адресний хід. Значення даних описується десятковим числом у діапазоні від -32768 до 32767. Режим адресації описується однобуквеним префіксом до значення даних.

mode    name               prefix
0       immediate          (none)
1       direct             A
2       indirect           B
3       double-indirect    C 

Приклад коду

Послідовність Фібоначчі в п'яти рядках:

0. MLZ -1 1 1;    initial value
1. MLZ -1 A2 3;   start loop, shift data
2. MLZ -1 A1 2;   shift data
3. MLZ -1 0 0;    end loop
4. ADD A2 A3 1;   branch delay slot, compute next term

Цей код обчислює послідовність Фібоначчі, RAM-адреса 1 містить поточний термін. Він швидко переповнює після 28657.

Сірий код:

0. MLZ -1 5 1;      initial value for RAM address to write to
1. SUB A1 5 2;      start loop, determine what binary number to covert to Gray code
2. SRL A2 1 3;      shift right by 1
3. XOR A2 A3 A1;    XOR and store Gray code in destination address
4. SUB B1 42 4;     take the Gray code and subtract 42 (101010)
5. MNZ A4 0 0;      if the result is not zero (Gray code != 101010) repeat loop
6. ADD A1 1 1;      branch delay slot, increment destination address

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

Інтернет-перекладач

El'endia Starman створив дуже корисний онлайн - перекладач тут . Ви можете переглядати код, встановлювати точки перерви, виконувати ручне записування в ОЗУ та візуалізувати ОЗУ як дисплей.

Cogol

Після того, як архітектура та мова складання були визначені, наступним кроком на «програмній» стороні проекту було створення мови вищого рівня, щось підходяще для Tetris. Таким чином я створив Cogol . Назва є як каламбур на "COBOL", так і абревіатура для "C of Game of Life", хоча варто відзначити, що Cogol - це C, що наш комп'ютер - власне комп'ютер.

Cogol існує на рівні трохи вище мови складання. Як правило, більшість рядків у програмі Cogol відповідають одному рядку складання, але є деякі важливі особливості мови:

  • Основні функції включають названі змінні з призначеннями та оператори, які мають більш читабельний синтаксис. Наприклад, ADD A1 A2 3стає z = x + y;, коли компілятор відображає змінні на адреси.
  • Циклічні конструкції, такі як if(){}, while(){}і do{}while();так, компілятор обробляє розгалуження.
  • Одновимірні масиви (з арифметикою вказівника), які використовуються для дошки Tetris.
  • Підпрограми та стек дзвінків. Вони корисні для запобігання дублювання великих фрагментів коду та для підтримки рекурсії.

Компілятор (який я написав з нуля) дуже базовий / наївний, але я намагався оптимізувати декілька мовних конструкцій, щоб досягти короткої тривалості складеної програми.

Ось декілька коротких оглядів роботи різних функцій мови:

Токенізація

Вихідний код токенізується лінійно (однопрохідний), використовуючи прості правила, щодо яких символи можуть бути суміжними в токені. Коли зустрічається символ, який не може бути суміжним з останнім символом поточного маркера, поточний маркер вважається завершеним, а новий символ починає новий маркер. Деякі символи (наприклад, {або ,) не можуть примикати до будь-яких інших символів і тому є їх власним маркером. Інші (як >і =) дозволяється тільки бути поруч з іншими персонажами в рамках свого класу, і таким чином можуть утворювати маркери , такі як >>>, ==або >=, але не подобається =2. Символи пробілів примушують межу між маркерами, але самі не включаються в результат. Найскладніший персонаж для токенізації - це- тому що він може представляти як віднімання, так і одинарне заперечення, і, отже, вимагає певного кожуха.

Розбір

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

Розподіл глобальної пам'яті

Компілятор призначає кожній глобальній змінній (слову чи масиву) свої власні призначені RAM-адреси. Необхідно оголосити всі змінні за допомогою ключового словаmy щоб компілятор знав виділити для нього простір. Набагато крутіше, ніж названі глобальні змінні, - це управління пам'яттю адреси подряпин. Багато інструкцій (зокрема умовні умови та багато доступу до масиву) вимагають тимчасових "подряпин" адрес для зберігання проміжних обчислень. Під час процесу компіляції компілятор при необхідності виділяє та виділяє адреси подряпин. Якщо компілятору потрібно більше адрес нуля, він буде виділяти більше оперативної пам’яті як адреси скретчу. Я вважаю, що для програми типово потрібно лише кілька адрес нуля, хоча кожна адреса нуля буде використовуватися багато разів.

IF-ELSE Заяви

Синтаксис if-elseвисловлювань є стандартною формою С:

other code
if (cond) {
  first body
} else {
  second body
}
other code

При перетворенні в QFTASM код розташовується так:

other code
condition test
conditional jump
first body
unconditional jump
second body (conditional jump target)
other code (unconditional jump target)

Якщо перший корпус виконаний, другий корпус пропускається. Якщо перший корпус пропущений, другий корпус виконується.

У зборі тест на стан зазвичай є лише відніманням, і ознака результату визначає, чи слід робити стрибок або виконувати тіло. MLZІнструкція використовується для обробки нерівності , такі як >або <=. Для MNZобробки використовується інструкція ==, оскільки вона перестрибує тіло, коли різниця не дорівнює нулю (і, отже, аргументи не рівні). Наразі багатовиразні умови не підтримуються.

Якщо elseоператор пропущено, безумовний стрибок також опущений, і код QFTASM виглядає так:

other code
condition test
conditional jump
body
other code (conditional jump target)

WHILE Заяви

Синтаксис whileвисловлювань також є стандартною формою С:

other code
while (cond) {
  body
}
other code

При перетворенні в QFTASM код розташовується так:

other code
unconditional jump
body (conditional jump target)
condition test (unconditional jump target)
conditional jump
other code

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

MLZІнструкція використовується для обробки нерівності , такі як >або <=. На відміну від ifвисловлювань, MNZдля обробки використовується інструкція !=, оскільки вона стрибає до тіла, коли різниця не дорівнює нулю (а отже, коли аргументи не рівні).

DO-WHILE Заяви

Єдина відмінність між whileі do-whileполягає в тому, що do-whileтіло циклу спочатку не пропускається, тому воно завжди виконується принаймні один раз. Я, як правило, використовую do-whileоператори, щоб зберегти пару рядків коду складання, коли я знаю, що цикл ніколи не потрібно буде повністю пропускати.

Масиви

Одновимірні масиви реалізуються як суміжні блоки пам'яті. Усі масиви мають фіксовану довжину на основі їх декларації. Масиви оголошуються так:

my alpha[3];               # empty array
my beta[11] = {3,2,7,8};   # first four elements are pre-loaded with those values

Для масиву це можливе відображення оперативної пам'яті, що показує, як адреси 15-18 зарезервовані для масиву:

15: alpha
16: alpha[0]
17: alpha[1]
18: alpha[2]

Позначена адреса alphaзаповнюється вказівником на місце розташування alpha[0], тому в цьому випадку випадок 15 містить значення 16. alphaЗмінна може використовуватися всередині коду Cogol, можливо, як вказівник стека, якщо ви хочете використовувати цей масив як стек .

Доступ до елементів масиву здійснюється за допомогою стандартних array[index]позначень. Якщо значення indexє постійним, ця посилання автоматично заповнюється абсолютною адресою цього елемента. В іншому випадку він виконує деяку арифметику вказівника (просто додавання), щоб знайти потрібну абсолютну адресу. Також можливо індексація гнізд, наприклад alpha[beta[1]].

Підпрограми та виклики

Підпрограми - це блоки коду, які можна викликати з декількох контекстів, запобігаючи дублюванню коду і дозволяючи створювати рекурсивні програми. Ось програма з рекурсивною підпрограмою для генерування чисел Фібоначчі (в основному найповільніший алгоритм):

# recursively calculate the 10th Fibonacci number
call display = fib(10).sum;
sub fib(cur,sum) {
  if (cur <= 2) {
    sum = 1;
    return;
  }
  cur--;
  call sum = fib(cur).sum;
  cur--;
  call sum += fib(cur).sum;
}

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

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

RAM map:
0: pc
1: display
2: scratch0
3: fib
4: scratch1
5: scratch2
6: scratch3
7: call

fib map:
0: return
1: previous_call
2: cur
3: sum

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

Існує кілька способів викликати підпрограму, використовуючи callключове слово:

call fib(10);   # subroutine is executed, no return vaue is stored

call pointer = fib(10);   # execute subroutine and return a pointer
display = pointer.sum;    # access a local variable and assign it to a global variable

call display = fib(10).sum;   # immediately store a return value

call display += fib(10).sum;   # other types of assignment operators can also be used with a return value

Будь-яка кількість значень може бути задана як аргумент для виклику підпрограми. Будь-який аргумент, що не надається, буде заповнений його значенням за замовчуванням, якщо воно є. Аргумент, який не надається і не має значення за замовчуванням, не очищається (щоб зберегти інструкції / час), тому потенційно може набути будь-якого значення на початку підпрограми.

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

Налагодження міток

Будь- {...}якому кодовому блоку в програмі Cogol може передувати багатословна описова мітка. Ця мітка додається як коментар до складеного коду асемблери та може бути дуже корисною для налагодження, оскільки полегшує пошук конкретних фрагментів коду.

Оптимізація слота затримки філії

Щоб підвищити швидкість компільованого коду, компілятор Cogol виконує деяку дійсно основну оптимізацію слота затримки як остаточне проходження коду QFTASM. Для будь-якого безумовного стрибка з порожнім слотом затримки гілки, проміжок затримки може бути заповнений першою інструкцією в пункті стрибка, а пункт стрибка збільшується на один, щоб вказувати на наступну інструкцію. Це, як правило, економить один цикл кожного разу, коли виконується безумовний стрибок.

Написання коду тетрісу в Коголі

Остаточна програма Tetris була написана на Cogol, а вихідний код доступний тут . Складений код QFTASM доступний тут . Для зручності тут надається постійна посилання: Tetris in QFTASM . Оскільки мета полягала в тому, щоб збільшити код збірки (а не код Когола), результат кого Когола непростий. Багато частин програми зазвичай розташовуються в підпрограмах, але ці підпрограми насправді були досить короткими, що дублювання коду зберігало інструкції надcallзаяви. Кінцевий код має лише одну підпрограму на додаток до основного коду. Крім того, багато масивів було видалено і замінено або на еквівалентно довгий список окремих змінних, або на безліч твердо кодованих чисел у програмі. Остаточний складений код QFTASM знаходиться під 300 інструкціями, хоча він лише трохи довший, ніж сам джерело Cogol.


22
Мені подобається, що вибір інструкцій з мовлення збірки визначається вашим обладнанням для підкладки (немає МЕЗ, тому що скласти правду з двох помилок важко). Фантастичне читання.
AlexC

1
Ви сказали, що =може стояти лише поруч із собою, але є !=.
Фабіан Рьолінг

@Fabian and a+=
Оліфант

@Oliphaunt Так, моє опис було не зовсім точним, це скоріше річ із класів символів, де певний клас персонажів може бути сусіднім один з одним.
PhiNotPi

606

Частина 5: Асамблея, переклад та майбутнє

З нашою програмою складання від компілятора настав час зібрати ПЗУ для комп’ютера Varlife та перекласти все у великий GoL-шаблон!

Асамблея

Збірка програми складання в ПЗУ виконується приблизно так само, як і в традиційному програмуванні: кожна інструкція переводиться у двійковий еквівалент, а потім об'єднуються у великий двійковий блок, який ми називаємо виконуваним. Для нас єдина відмінність полягає в тому, що бінарний блоб потрібно перекласти в схеми Varlife і приєднати до комп'ютера.

K Zhang написав CreateROM.py , сценарій Python для Golly, який робить збірку та переклад. Це досить просто: він бере програму складання з буфера обміну, збирає її у двійковий і переводить це бінарне в схему. Ось приклад із простим тестером первинності, включеним до сценарію:

#0. MLZ -1 3 3;
#1. MLZ -1 7 6; preloadCallStack
#2. MLZ -1 2 1; beginDoWhile0_infinite_loop
#3. MLZ -1 1 4; beginDoWhile1_trials
#4. ADD A4 2 4;
#5. MLZ -1 A3 5; beginDoWhile2_repeated_subtraction
#6. SUB A5 A4 5;
#7. SUB 0 A5 2;
#8. MLZ A2 5 0;
#9. MLZ 0 0 0; endDoWhile2_repeated_subtraction
#10. MLZ A5 3 0;
#11. MNZ 0 0 0; endDoWhile1_trials
#12. SUB A4 A3 2;
#13. MNZ A2 15 0; beginIf3_prime_found
#14. MNZ 0 0 0;
#15. MLZ -1 A3 1; endIf3_prime_found
#16. ADD A3 2 3;
#17. MLZ -1 3 0;
#18. MLZ -1 1 4; endDoWhile0_infinite_loop

Це створює наступне двійкове:

0000000000000001000000000000000000010011111111111111110001
0000000000000000000000000000000000110011111111111111110001
0000000000000000110000000000000000100100000000000000110010
0000000000000000010100000000000000110011111111111111110001
0000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000011110100000000000000100000
0000000000000000100100000000000000110100000000000001000011
0000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000110100000000000001010001
0000000000000000000000000000000000000000000000000000000001
0000000000000000000000000000000001010100000000000000100001
0000000000000000100100000000000001010000000000000000000011
0000000000000001010100000000000001000100000000000001010011
0000000000000001010100000000000000110011111111111111110001
0000000000000001000000000000000000100100000000000001000010
0000000000000001000000000000000000010011111111111111110001
0000000000000000010000000000000000100011111111111111110001
0000000000000001100000000000000001110011111111111111110001
0000000000000000110000000000000000110011111111111111110001

У перекладі на схеми Varlife це виглядає приблизно так:

ПЗУ

крупним планом ROM

Потім ПЗУ пов'язаний з комп'ютером, який формує повністю функціонуючу програму в Varlife. Але ми ще не закінчили ...

Переклад «Гра життя»

Весь цей час ми працювали в різних шарах абстракції над базою гри Життя. Але зараз настав час зняти завісу абстракції і перевести нашу роботу в модель «Гра життя». Як вже було сказано раніше, ми використовуємо OTCA Metapixel в якості основи для Varlife. Отже, завершальним кроком є ​​перетворення кожної комірки в Varlife в метапіксель у грі життя.

На щастя, Golly оснащений сценарієм ( metafier.py ), який може конвертувати шаблони в різних наборах правил для моделей Game of Life за допомогою метапікселя OTCA. На жаль, він призначений лише для перетворення шаблонів з єдиним глобальним набором правил, тому він не працює на Varlife. Я написав модифіковану версію, яка вирішує цю проблему, так що правило для кожного метапікселя генерується на основі клітини на клітку для Varlife.

Отже, наш комп’ютер (із ПЗУ Tetris) має обмежувальну коробку розміром 1436 х 5,082. З 7,297,752 клітинок у цьому полі 6,075,811 - це порожній простір, що залишає фактичну кількість чисельності 1221 941. Кожну з цих комірок необхідно перевести у метапіксель OTCA, який має обмежувальну рамку 2048x2048 та сукупність або 64 691 (для метапікселя ON), або 23 920 (для метапікселя OFF). Це означає, що кінцевий продукт матиме обмежувальну коробку в розмірі 2 940 928 х 10 407 936 (плюс кілька тисяч додаткових для меж метапікселів) з населенням від 29,228,828,720 до 79,048,585,231. Маючи 1 біт на живу клітинку, це від 27 до 74 Гбіт, необхідних для представлення всього комп'ютера та ПЗУ.

Я включив ці розрахунки сюди, тому що я нехтував запускати їх перед запуском сценарію і дуже швидко втратив пам'ять на своєму комп’ютері. Після панічної killкоманди я здійснив модифікацію сценарію метафіксатора. Кожні 10 рядків метапікселів шаблон зберігається на диску (у вигляді gzipped RLE-файлу), а сітка розмивається. Це додає додатковий час виконання для перекладу і використовує більше місця на диску, але зберігає використання пам'яті у прийнятних межах. Оскільки Golly використовує розширений формат RLE, що включає розташування шаблону, це не додає більше труднощів до завантаження шаблону - просто відкрийте всі файли шаблону на одному шарі.

К. Чжан вибухнув із цієї роботи і створив більш ефективний сценарій метафіксаторів який використовує формат файлів MacroCell, який завантажує ефективніше, ніж RLE для великих шаблонів. Цей сценарій працює значно швидше (за кілька секунд, порівняно з кількома годинами для оригінального сценарію метафіксатора), створює значно менший вихід (121 Кб проти 1,7 ГБ) і може метафікувати весь комп’ютер та ПЗУ одним махом, не використовуючи значної кількості пам'яті. Скористається тим, що файли MacroCell кодують дерева, що описують шаблони. За допомогою користувацького файлу шаблону метапікселі попередньо завантажуються у дерево, а після деяких обчислень та модифікацій для виявлення сусідів шаблон Varlife можна просто додати.

Файл шаблонів усього комп’ютера та ПЗУ в грі життя можна знайти тут .


Майбутнє проекту

Тепер, коли ми зробили тетріс, ми закінчили, правда? Навіть близько не. У нас є ще кілька цілей цього проекту, над яким ми працюємо:

  • Каламутна критика та Критіксі Літос продовжують роботу над мовою вищого рівня, яка складається з QFTASM.
  • El'endia Starman працює над оновленням онлайн-інтерпретатора QFTASM.
  • Quartata працює над GCC, що дозволить компілювати вільностоячий код C та C ++ (та потенційно інших мов, таких як Fortran, D або Objective-C) в QFTASM через GCC. Це дозволить створювати більш складні програми більш звичною мовою, хоча і без стандартної бібліотеки.
  • Одне з найбільших перешкод, яке нам доводиться долати, щоб досягти більшого прогресу, - це той факт, що наші інструменти не можуть видавати незалежний від позиції код (наприклад, відносні стрибки). Без PIC ми не можемо здійснювати жодних зв'язків, і тому ми пропускаємо переваги, які виникають у зв'язку з можливістю посилання на існуючі бібліотеки. Ми працюємо над тим, щоб знайти спосіб зробити PIC правильно.
  • Ми обговорюємо наступну програму, яку хочемо написати для комп'ютера QFT. Зараз Понг виглядає як приємний гол.

2
Тільки дивлячись на майбутній підрозділ, чи не відносний стрибок просто ADD PC offset PC? Вибачте, якщо це неправильно, програмування монтажу ніколи не було моєю форте.
MBraedley

3
@Timmmm Так, але дуже повільно. (Ви також повинні використовувати HashLife).
спагетто

75
Наступною програмою, яку ви пишете для неї, повинна бути гра «Життя життя Конвея».
ACK_stoverflow

13
@ACK_stoverflow Це буде зроблено в якийсь момент.
Mego

13
У вас є відео про це?
PyRulez

583

Частина 6: Новий компілятор для QFTASM

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

Ми побудували мову низького рівня з подібним синтаксисом до Python, включаючи просту систему типу, підпрограми, що підтримують рекурсію та вбудовані оператори. Компілятор від тексту до QFTASM був створений за допомогою 4 кроків: маркера, дерева граматики, компілятора високого рівня та компілятора низького рівня.

Токенізатор

Розробка була розпочата з використанням Python за допомогою вбудованої бібліотеки токенізер, тобто цей крок був досить простим. Потрібно було внести лише кілька змін до виводу за замовчуванням, включаючи зачистки коментарів (але не #includes).

Граматичне дерево

Дерево граматики було створене для легкого розширення, не змінюючи жодного вихідного коду.

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

Граматика, необхідна для підтримки повторних вузлів, а також необов'язкових. Це було досягнуто шляхом введення метатегів для опису того, як слід читати лексеми.

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

Компіляція у код високого рівня

Кожна особливість мови повинна бути спроможна скластись у конструкції високого рівня. До них відносяться assign(a, 12) і call_subroutine(is_prime, call_variable=12, return_variable=temp_var). Такі функції, як вбудовування елементів, виконуються в цьому сегменті. Вони визначаються як operators і особливі тим, що вони вкладаються кожен раз, коли такий оператор, як-от +або %використовується. Через це вони більш обмежені, ніж звичайний код - вони не можуть використовувати власного оператора, ані будь-якого оператора, який покладається на визначений.

Під час процесу вбудовування внутрішні змінні замінюються на викликані. Це по суті обертається

operator(int a + int b) -> int c
    return __ADD__(a, b)
int i = 3+3

в

int i = __ADD__(3, 3)

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

Змінні скретчу та складні операції

Математичні операції, такі як a += (b + c) * 4неможливо обчислити без використання додаткових комірок пам'яті. Компілятор високого рівня займається цим шляхом розділення операцій на різні розділи:

scratch_1 = b + c
scratch_1 = scratch_1 * 4
a = a + scratch_1

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

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

Структура ОЗУ

Program counter
Subroutine locals
Operator locals (reused throughout)
Scratch variables
Result variable
Stack pointer
Stack
...

Компіляція низького рівня

Єдине , що компілятор низький рівень має справу з якими sub, call_sub, return, assign, ifі while. Це значно скорочений перелік завдань, які можна легше перекласти в інструкції QFTASM.

sub

Тут знаходяться початок і кінець названої підпрограми. Компілятор низького рівня додає мітки, а у випадку mainпідпрограми додає інструкцію щодо виходу (перехід до кінця ПЗУ).

if і while

І перекладачі, whileі ifнизькі рівні досить прості: вони отримують вказівники на свої умови і стрибають залежно від них. whileпетлі трохи відрізняються тим, що вони складені як

...
condition
jump to check
code
condition
if condtion: jump to code
...

call_sub і return

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

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

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

assign

Змінні завдання - це найпростіші речі для компілювання: вони беруть змінну та значення та компілюють в єдиний рядок: MLZ -1 VALUE VARIABLE

Призначення стрибкових цілей

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

Приклад покрокової компіляції

Тепер, коли ми пройшли всі етапи, переходимо крок за кроком до фактичного процесу компіляції для фактичної програми.

#include stdint

sub main
    int a = 8
    int b = 12
    int c = a * b

Гаразд, досить просто. Це повинно бути очевидно , що в кінці програми, a = 8, b = 12, c = 96. По-перше, давайте включати відповідні частини stdint.txt:

operator (int a + int b) -> int
    return __ADD__(a, b)

operator (int a - int b) -> int
    return __SUB__(a, b)

operator (int a < int b) -> bool
    bool rtn = 0
    rtn = __MLZ__(a-b, 1)
    return rtn

unsafe operator (int a * int b) -> int
    int rtn = 0
    for (int i = 0; i < b; i+=1)
        rtn += a
    return rtn

sub main
    int a = 8
    int b = 12
    int c = a * b

Гаразд, трохи складніше. Давайте перейдемо на маркер і подивимося, що виходить. На цьому етапі у нас буде лише лінійний потік жетонів без будь-якої форми структури

NAME NAME operator
LPAR OP (
NAME NAME int
NAME NAME a
PLUS OP +
NAME NAME int
NAME NAME b
RPAR OP )
OP OP ->
NAME NAME int
NEWLINE NEWLINE
INDENT INDENT     
NAME NAME return
NAME NAME __ADD__
LPAR OP (
NAME NAME a
COMMA OP ,
NAME NAME b
RPAR OP )
...

Тепер усі лексеми переносяться через граматичний аналізатор і виводять дерево з іменами кожного з розділів. Це показує структуру високого рівня, яку читає код.

GrammarTree file
 'stmts': [GrammarTree stmts_0
  '_block_name': 'inline'
  'inline': GrammarTree inline
   '_block_name': 'two_op'
   'type_var': GrammarTree type_var
    '_block_name': 'type'
    'type': 'int'
    'name': 'a'
    '_global': False

   'operator': GrammarTree operator
    '_block_name': '+'

   'type_var_2': GrammarTree type_var
    '_block_name': 'type'
    'type': 'int'
    'name': 'b'
    '_global': False
   'rtn_type': 'int'
   'stmts': GrammarTree stmts
    ...

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

('sub', 'start', 'main')
('assign', int main_a, 8)
('assign', int main_b, 12)
('assign', int op(*:rtn), 0)
('assign', int op(*:i), 0)
('assign', global bool scratch_2, 0)
('call_sub', '__SUB__', [int op(*:i), int main_b], global int scratch_3)
('call_sub', '__MLZ__', [global int scratch_3, 1], global bool scratch_2)
('while', 'start', 1, 'for')
('call_sub', '__ADD__', [int op(*:rtn), int main_a], int op(*:rtn))
('call_sub', '__ADD__', [int op(*:i), 1], int op(*:i))
('assign', global bool scratch_2, 0)
('call_sub', '__SUB__', [int op(*:i), int main_b], global int scratch_3)
('call_sub', '__MLZ__', [global int scratch_3, 1], global bool scratch_2)
('while', 'end', 1, global bool scratch_2)
('assign', int main_c, int op(*:rtn))
('sub', 'end', 'main')

Потім компілятор низького рівня повинен перетворити це представлення високого рівня в код QFTASM. Змінні призначаються місцями в оперативній пам'яті так:

int program_counter
int op(*:i)
int main_a
int op(*:rtn)
int main_c
int main_b
global int scratch_1
global bool scratch_2
global int scratch_3
global int scratch_4
global int <result>
global int <stack>

Потім складаються прості інструкції. Нарешті, додаються номери інструкцій, що призводить до виконуваного коду QFTASM.

0. MLZ 0 0 0;
1. MLZ -1 12 11;
2. MLZ -1 8 2;
3. MLZ -1 12 5;
4. MLZ -1 0 3;
5. MLZ -1 0 1;
6. MLZ -1 0 7;
7. SUB A1 A5 8;
8. MLZ A8 1 7;
9. MLZ -1 15 0;
10. MLZ 0 0 0;
11. ADD A3 A2 3;
12. ADD A1 1 1;
13. MLZ -1 0 7;
14. SUB A1 A5 8;
15. MLZ A8 1 7;
16. MNZ A7 10 0;
17. MLZ 0 0 0;
18. MLZ -1 A3 4;
19. MLZ -1 -2 0;
20. MLZ 0 0 0;

Синтаксис

Тепер, коли у нас є гола мова, ми маємо насправді написати в ній невелику програму. Ми використовуємо відступи, як це робить Python, розділяючи логічні блоки та керуючи потоком. Це означає, що пробіл важливий для наших програм. Кожна повна програма має mainпідпрограму, яка діє так само, як main()функція на мовах, подібних С. Функція запускається на початку програми.

Змінні та типи

Коли змінні визначаються вперше, їм потрібно мати тип, пов'язаний з ними. В даний час визначено типи intі boolз синтаксисом для масивів , визначених , але не компілятор.

Бібліотеки та оператори

Доступна бібліотека stdint.txt, яка визначає основні оператори. Якщо це не включено, навіть прості оператори не будуть визначені. Ми можемо використовувати цю бібліотеку за допомогою #include stdint. stdintвизначає такі оператори, як +, >>і навіть, *і %жоден з яких не є прямими кодами QFTASM.

Мова також дозволяє прямим викликом QFTASM-кодів __OPCODENAME__.

Додавання в stdintвизначається як

operator (int a + int b) -> int
    return __ADD__(a, b)

Що визначає, що +робить оператор, коли йому задано два ints.


1
Чи можу я запитати, чому було вирішено створити CA, схожий на провідний світ у грі життя Конвей, та створити новий процесор за допомогою цієї схеми, а не повторно використовувати / доопрацювати існуючий універсальний комп'ютер cgol, такий як цей ?
eaglgenes101

4
@ eaglgenes101 Для початку я не думаю, що більшість із нас знали про існування інших універсальних комп’ютерів, які можна використовувати. Ідея про CA, що нагадує провідний світ з декількома змішаними правилами, виникла в результаті розігрування металевих елементів (я вважаю, що Фі прийшов до ідеї). Звідти це було логічним прогресом до того, що ми створили.
Mego
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.