Міністерство освіти і науки Національний університет «Львівська політехніка» Пояснювальна записка до курсової роботи з дисципліни: “Архітектура комп’ютерів” на тему: “Проектування комп’ютера” Виконав: ст.гр. КІ-37 Грушецький Н.М. Перевірив: Грицик І.В. Львів 2021 1 Анотація Курсова робота складається з трьох частин: перша частина являє собою розробку програми, яка перетворює вхідну програму на мові асемблер в мову машинних кодів. В цій частині проводиться аналіз всіх команд, які потрібно реалізувати, а також розробка тестів для тестування даної програми. В другій частині здійснюється розробка симулятора, який може відсимулювати машинний код, а також розробка тестів для тестування розробленої програми. Третє частина являє собою розробку асемблерної програми для перевірки коректності роботи моделі спрощеного комп’ютера. Програма має бути ефективною: - не перевищувати 50 рядків; - і не має виходити за 1000 інструкцій для будь-яких вхідних даних. 2 Зміст Анотація ........................................................................................................................................ 2 Вихідні дані на проектування ................................................................................................... 4 1. Аналітичний розділ ........................................................................................................ 7 1.1. Система команд........................................................................................ 11 1.2. Способи адресації ..................................................................................... 12 2. ПРОЕКТНИЙ РОЗДІЛ ................................................................................................ 14 2.1. Алгоритми роботи розробленого симулятора і асемблера ..................... 14 2.2.Функціональна схема комп’ютера до модифікації .................................. 20 2.3. Функціональна схема комп’ютера після модифікації ............................. 22 2.4. Множина інструкцій комп’ютера ............................................................ 23 2.5. Опис форматів інструкцій розробленого комп’ютера .......................... 25 2.6. Опис виконання кожного типу інструкцій в по тактовому режимі ... 27 3. Тестовий розділ ...................................................................................................................... 29 Використана література ........................................................................................................... 37 Висновок...................................................................................................................................... 38 Додатки ........................................................................................................................................ 39 3 Вихідні дані на проектування Перелік завдань: 1. 8 додаткових інструкцій без використання регістрів стану: 3 – арифметичні 3 – логічні 2 – керування 2. 3 додаткові інструкції з використання регістрів стану. 3. Передбачити на власний вибір 3 інструкцій (з розроблених в п. 1, 2), які підтримують додатковий тип адресації. Таблиця 1. Варіант №11. Варіант 11 Арифметичні 1 11 1. 4 2 3 6 3 10 Логічні 4 5 6 4 8 10 Керуван ня Прапорці 7 1 1 8 Адресація № Розряд № ність 2 3 17 CF 2 3 4 3 Визначити формати команд згідно з розрядністю шини даних, розміру пам’яті та регістрового файлу. Таблиця 2. Розрядність. № 4 2. Розрядність шини Розмір пам’яті Розмір регістрового даних Байт файлу(к-сть регістрів) 40 1048576 32 Реалізація додаткових команд. Необхідно реалізувати 8 додаткових команд. Серед них 3 арифметичні, 3 логічні та 2 команди керування згідно з варіантом. Команди не мають повторюватися. 4 Таблиця 3. Арифметичні команди. № Мнемонічнй код Зміст 3 DIV regA regB destReg Беззнакове ділення destReg=regA/regB 6 XADD regA regB destReg Додати і обміняти операнди місцями destReg=regA+regB regA<=>regB 10 XIMUL destReg regA regB Знакове множення і обмін операндів місцями destReg=regA*regB regA<=>regB Таблиця 4. Логічні команди. № Мнемонічнй код Зміст 4 SHR regA regB destReg 8 ROR regA regB destReg Циклічний зсув вправо destReg=regA << regB 10 OR regA regB destReg Логічний зсув вправо destReg=regA >> regB Логічне побітове АБО destReg=regA | regB Таблиця 5. Команди керування. № 1 Мнемонічнй код JMA regA regB offSet 17 JMNLE offSet 3. regA Зміст Беззнакове більше if (regA> regB) PC=PC+1+offSet regB Знакове не менше/рівно PC=PC+1+offSet if (regA!<= regB) Реалізувати додатковий спосіб адресації. Передбачити, що 3 інструкції підтримують інший вид адресації згідно з варіантом. Визначення операндів, які підтримують інший спосіб адресації узгодити з викладачем. (крім безадресної) Примітка: безадресний варіант передбачає створення стеку та реалізацію 2 додаткових команд наведених в таблиці. 5 Таблиця 6. Додаткова адресація. № Адресація 3 Непряма 4. Регістри стану: CF –регістр переносу, SF – регістр знаку, ZF – регістр 0. Таблиця 7. Регістр переносу CF. № Мнемонічнй код Зміст 2 SBB regA regB destReg Віднімання з переносом: destReg=regA-regB-СF 3 BT regA regB Копіює значення біта з regA по позиції regB в CF = regA[regB] 4 CMP regA regB Порівняти regA regB і встановити прапорці СF SF ZF regA < regB 1 1 0 regA = regB 0 0 1 regA > regB 0 0 0 6 1. Аналітичний розділ CISC (англ. Complex Instruction Set Computer — комп'ютер зі складним набором команд) — це архітектура системи команд, в якій більшість команд є комплексними, тобто реалізують певний набір простіших інструкцій процесора або шляхом зіставлення з кожною CISC-командою певної мікропрограми, або принаймні можуть бути зведені до набору таких простих інструкцій. Крім того, ознаками CISC-архітектури можна вважати також наявність великої кількості методів адресації пам'яті з можливістю безпосередньої роботи з операндами в основній пам'яті комп'ютера. Тобто, CISC-архітектури відносяться, як правило, до класу двохадресних. Архітектури з комплексними наборами команд, розвиток яких припав на кінець 60-х — 70-ті роки пропонували програмісту досить різноманітний набір порівняно високорівневих інструкцій машинної мови, таких, наприклад, як «виклик підпрограми» або «відняти одиницю та перейти, якщо результат ненульовий», а також велику кількість способів звертання до операндів в пам'яті для полегшення роботи зі складними структурами даних. У ті часи, за відсутності повноцінних мов відповідних компіляторів, інструментарію така програмування програмування апаратна могла високого підтримка рівня та високорівневого підвищити продуктивність праці програміста. До того ж, програма, складена з таких команд займала небагато в пам'яті комп'ютера. Типовими прикладами CISC-архітектур були системи VAX, PDP- 11, IBM System/360, сімейства мікропроцесорів Motorola 68000 та Intel x86. Але з появою високорівневих мов та оптимізуючих компіляторів, розвитком електроніки, який спричинив здешевлення комп'ютерної пам'яті виявилось, що використання високорівневих машинних команд суттєво обмежує можливості до оптимізації програми, підвищення її швидкодії. Зокрема, складні команди потребували багато часу на процедуру декодування, потребували багато апаратурних ресурсів для реалізації, що негативно відображалось на загальній швидкодії та складності системи. Далі, 7 наявність спеціальних команд, таких як «виклик підпрограми» не завжди виправдовувала себе, в багатьох випадках доцільніше було б замість такої загальної команди використати набір елементарніших інструкцій, які в результаті спричиняли б виконання меншої кількості обчислень процесором. Це стосувалось і обчислювальних команд, які підтримували роботу з операндами в повільній пам'яті, що далеко не завжди давало оптимальний результат. Набагато ефективніше було б відокремити процедури роботи з пам'яттю (завантаження та збереження операндів) від проведення обчислень, що дало б можливість оптимізувати процедури звертання до запам'ятовуючого пристрою. Зрозуміло, що ці оптимізації повинні були виконуватись вже компіляторами з мов високого рівня, які до того часу досягли досить високого рівня функціональності. Ці та інші проблеми CISC-архітектур призвели до створення в 80-ті роки RISC-архітектур (від англ. Reduced Instruction Set Computer — комп'ютер зі скороченим набором команд), які вибудовують прямо протилежну модель системи команд з максимальним спрощенням семантики машинної команди, зведенням її до елементарної, мінімізацією методів адресації пам'яті тощо та принциповою орієнтацією на мови високого рівня й оптимізуючі компілятори з них, аніж на програмування безпосередньо в машинних мовах. Сучасні CISC-архітектури, такі як останні втілення сімейства процесорів x86, хоч і відповідають CISC-концепції на рівні архітектури системи команд, але всередині процесора реалізують якраз пристосованішу до сьогоднішніх реалій RISC-модель, трансформуючи потік CISC-команд в процесі виконання в набори з простіших RISC-мікрооперацій, які й виконуються процесором. Для виконання задачі на комп’ютері необхідно: забезпечити вибірку команди програми із його пам’яті в заданій послідовності, організувати звернення до неї за відповідними адресами; забезпечити розпізнавання типів виконуваних операцій; 8 організувати звернення до пам’яті за відповідними адресами для вибірки необхідних для виконання кожної команди даних; організувати виконання над даними операцій відповідно до вказівок команд; запам’ятати результат обчислень. Комп'ютер виконує кожну команду як послідовність простих операцій: 1. Вибірка чергової команди із основної пам'яті. 2. Визначення типу вибраної команди, тобто її дешифрування. 3. Визначення адрес даних, необхідних для виконання цієї команди. 4. Виконання операцій пересилання даних (зчитування даних із пам'яті в регістри процесора). 5. Виконання операції відповідно до її коду в полі коду операції команди. 6. Визначення адрес, за якими запам'ятовуються результати. 7. Запам'ятовування результатів. 8. Підготовка до виконання наступної команди, тобто обчислення її адреси. Для процесора комп'ютера із складною системою команд характерні наступні особливості: виконання команди за багато тактів, оскільки для цього потрібно здійснити багаторазові операції звернення до основної пам'яті та до програмно-доступних регістрів процесора; орієнтація АЛП на виконання великої кількості операцій, що пов'язано з розширеним складом системи команд; складна система розпізнавання команди, що пов'язано з великою кількістю методів адресації та великою кількістю форматів команд різної розрядності; програмне дешифрування команд з метою зменшення затрат обладнання; 9 складна організація конвеєризації виконання команд, що пов'язано, в першу чергу, з різнотипністю їх виконання; орієнтація структури на виконання команд типу регістр-пам'ять та пам'ять-пам'ять. Основні елементи процесора - арифметико-логічний пристрій, пристрій керування і регістрова пам'ять або, як її ще називають, надоперативний запам'ятовуючий пристрій. До складу регістрової пам'яті, в свою чергу, входять наступні вузли - програмний лічильник, регістри: адреси, команди, даних, слова стану програми, а також регістровий файл, який складається з програмно доступних регістрів. Структура регістрової (надоперативної) пам'яті процесора складається з регістрів спеціального та зального призначення. До регістрів спеціального призначення належать: регістри адреси (РгА); регістри команд (РгК); програмний лічильник(ПЛ) регістри даних (РгД). РгА зберігає адресу даного або команди при зверненні до основної пам'яті. РгД зберігає операнд при його запису або зчитуванні з основної пам'яті. В ролі операнда може бути дане, команда або адреса. РгК зберігає команду після її зчитування з основної пам'яті. ПЛ підраховує команди та зберігає адресу поточної команди. Комп'ютер з архітектурою Джона фон Неймана має один програмний лічильник. 10 1.1. Система команд Різноманітність типів даних, форм представлення та опрацювання, необхідні дії для обробки та керування ходом виконання обчислень призводить до необхідності використання різноманітних команд – набора команд. Кожен процесор має власний набір команд, який називається системою команд процесора. Система команд характеризується трьома аспектами: формат, способи адресації, система операцій. Форматом команди – є довжина команди, кількість, розмір, положення, призначення та спосіб кодування полів. Команди мають включати наступні види інформації: тип операції, яку необхідно реалізувати в даній команді (поле команду операції - КОП); місце в пам’яті звідки треба взяти перший операнд (А1); місце в пам’яті звідки треба взяти другий операнд (А2); місце в пам’яті куди треба помістити результат (А3). Кожному з цих видів інформації відповідає своя частина двійкового слова – поле. Реальна система команд зазвичай має команди декількох форматів, тип формату визначає КОП. Команда в комп'ютері зберігається в двійковій формі. Вона вказує тип операції, яка має бути виконаною, адреси операндів, над якими виконується операція, та адреси розміщення результатів виконання операції. Відповідно до цього команда складається з двох частин, коду операції та адресної частини. 11 1.2. Способи адресації Варіанти інтерпретації бітів (розрядів) поля адреси з метою знаходження операнда називаються способами адресації. Коли команда вказує на операнд, він може знаходитись в самій команді, в основній або зовнішній пам'яті чи в регістровій пам'яті процесора. За роки існування комп'ютерів була створена своєрідна технологія адресації, яка передбачає реалізацію різних способів адресації, чому послужило ряд причин: забезпечення ефективного використання розрядної сітки команди; забезпечення ефективної апаратної підтримки роботи з масивами даних; забезпечення задання параметрів операндів; можливість генерації великих адрес на основі малих. Існує велика кількість способів адресації. Розглянемо п’ять основних способів адресації операндів в командах. Пряма – в цьому випадку адресне поле зберігає адресу операнда. Її різновидом є пряма регістрова адресація, яка адресує не комірку пам’яті а номер регістру. Безпосередня – в поле адреси команди поміщається не адреса, а сам операнд. Непряма – в полі адреси команди зберігається адреса комірки пам’яті в якій знаходиться адреса операнда. Такій спосіб дозволяє оперувати з адресами як з даними. Різновид непряма-регістрова адресація, адреса адреси зберігається в регістрі загального призначення. Відносна – адреса формується, як сума з двох доданків: бази, яка зберігається в спеціальному регістрі чи в одному з регістрів спеціального призначення, та зміщення, яке задається в полі адреси команди. Різновид індексна та базова індексна. При індексній замість базового регістра є індексний, який автоматично модифікується (зазвичай збільшується на 1). Базова-індексна адресація формується адреса як сума трьох доданків: бази, індексу та зміщення. 12 Безадресна – поле адреси в команді відсутнє. Адреса операнда, або немає змісту або є по замовчуванню (наприклад дії на спеціальним регістром - акумулятором). Безадресні команди неможливо використати для інших регістрів чи комірок пам’яті. Одним з різновидів безадресної адресації є використання стеку. В команду вводяться спеціальні ознаки з тим, щоб пристрій керування міг розпізнати використаний спосіб. Це можуть бути додаткові розряди в команді, або для різних типів команд закріплюватись різні способи адресації. 13 2. ПРОЕКТНИЙ РОЗДІЛ 2.1. Алгоритми роботи розробленого симулятора і асемблера Перш ніж почати роботу над цією програмою, потрібно визначитись з синтаксисом асемблерної програми, яка буде використовуватись як вхідні дані програми – перетворювача. Отже, формат лінійки асемблерного коду буде наступний (<пробіл> означає послідовність табуляцій і/або пробілів): мітка <пробіл> інструкція<пробіл>поле№1<пробіл> поле№2<пробіл> поле№3<пробіл>коментар Де, поле№1-№3 – номер регістру або адреса (символьна або числова) в залежності від команди. Алгоритм перетворення асемблерних команд у машинні інструкції передбачає 2 «проходи» по вхідній текстовій (асемблерній) програмі. У процесі першого проходу програма перевіряє коректність синтаксису команд (Перевіряє кожну вхідну команду на наявність її у множині команд комп’ютера (Простіше кажучи, перевіряє, чи ця команда відома комп’ютеру), перевіряє відповідність кількості операндів вхідної команди та задекларованої кількості операндів для цієї команди, правильність зазначених у команді операндів (Операдом можуть бути: регістр, символьне зміщення (мітка), числове зміщення). 14 Відкриття файлу з текстом програми Program.as readAndParse label opcode arg0 arg1 arg2 testRegArg testAddrArg Рис. 2.1.1. Схема першого проходу асемблера. Примітка до рисунку 2.1.1: блок readAndParse зчитує з вхідного файлу 1 рядок і отримує з нього значення таких полів: мітка, код операції, операнд 1 (якщо операція цього потребує), операнд 2 (якщо операція цього потребує) та операнд 3 (якщо операція цього потребує). На цьому етапі перевіряється наявність цієї команди у комп’ютері, кількість та тип операндів. В залежності від команди, операнд (операнди) перевіряються на коректність (номеру регістра (для команд, які працюють з регістрами), символьної або числової адреси (для команд завантаження, зберігання даних, команд керування і т.д., де одним з операндів є адреса у пам’яті))). Блок testRegArg перевіряє правильність номеру регістру. На схемі, до цього блоку підведені всі 3 регістри. Це було обумовлено можливістю використання команд, в яких всі 3 операнди - регістри. (ADD reg1 reg2 reg3, DIV reg1 reg2 reg3, NAND reg1 reg2 reg3 і т.д.) testAddrArg перевіряє коректність вказання адреси (символьної або числової). На схемі, до цього блоку підведені тільки перший та третій операнди. Це обумовлено специфікацією команд розробленого комп’ютера. Тобто, у наборі інструкцій мого комп’ютера немає такої інструкції, у якій другим операндом буде адреса. Перший операнд, як адресу, може 15 використовувати тільки одна інструкція - .fill (збереження у мітці числа або адреси). Приклад цієї операції: one .fill 1 – Цей приклад «покладе» за адресою, де знаходиться ця команда одиницю. Доступитись до цієї адреси можна буде за допомогою мітки one. Грубо кажучи, виконалась операція one = 1; one .fill start – У цьому випадку в адресі, де знаходиться ця команда збережеться адреса мітки start. З третім операндом, як адресою, працюють операції завантаження\збереження даних, операції керування (lw,sw,jma,jmnl,beq) Program.as у схемі – це текстовий файл з програмою. У процесі другого проходу рядку програми виконується генерування відповідних машинних команд, тобто числового еквіваленту асемблерним командам. Зазираючи глибше у цей процес, можна сказати, що незалежно від операції, її код зсувається вліво на 24 розряди, цим самим, лишаючи за кодом 24 розряди, які використовуються для операндів (якщо операція їх потребує), 4 розряди для першого операнду, 4 – для другого і 16 – для третього (регістр або адреса)). 16 Рис. 2.1.2. Схема другого проходу асемблера. Блок isNumber перевіряє, чи є операнд числом. Якщо так, то програма записує це число у відповідній позиції адресного поля команди. Якщо ні, то переходить до блоку translateSymbol translateSymbol – Підставляє замість символьної адреси (мітки) числову. Числова адреса береться з, сформованої на першому проході, своєрідної таблиці міток, де вказані адреси для кожної мітки. Після виконання переходить до блоку формування машинного коду. Утворення машинного коду – Формує машинний код з коду операції та операндів. На виході цього блоку утворюється 2 файли: Program.mc – файл з послідовністю машинних інструкцій, який буде читатися програмою – симулятором. Ця програма покликана виконати послідовність машинних команд, які були сформовані програмою – перетворювачем . По суті, це виконуюча частина мого «Спрощеного комп’ютера». 17 Рис. 2.1.3. Функціональна схема симулятора Симулятор починає свою роботу ініціалізацією пам’яті та регістрів нульовими значеннями. Наступним кроком відбувається завантаження програми у машинних кодах у пам’ять з текстового файлу. Далі відбувається покрокове виконання програми (один крок – одна команда) та вивід стану у файл. (під виводом стану мається на увазі вивід вмістимого всіх задіяних комірок пам’яті та регістрів, вмістиме прапорців SF,CF,ZF, вмістиме ПЛ та мнемонічний запис останньої виконаної команди). У блоці stateStruct зберігається стан машини – значення регістрів, пам’яті, програмного лічильника та прапорця (CF). 18 Run – виконує вибірку інструкції з пам’яті, декодує інструкцію та виконує її. На етапі декодування викликається блок convertNum, який перетворює 20-ти розрядне число у число int, прийняте у «Сі-подібних» мовах як 40 розрядне. Також, у кінці кожного run циклу викликається блок printState, який «звітує» про пророблену роботу. (виводить стан машини, про це було зазначено вище). Додатковим завдання було - розробити додатковий спосіб адресації та передбачити, що 3 інструкції його підтримують. Адресація згідно з варіантом – непряма. Непряма адресація – в полі адреси команди зберігається адреса комірки пам’яті в якій знаходиться адреса операнда. Такій спосіб дозволяє оперувати з адресами як з даними. Різновид непряма-регістрова адресація, адреса адреси зберігається в регістрі загального призначення. 19 2.2.Функціональна схема комп’ютера до модифікації В спрощеному комп’ютері (СК) в пам’яті зберігаються, як дані так і інструкції. Кожна інструкція закодована числом. Це число складається з декількох полів: поле назви команди чи код операції (КОП) та полів операндів. В СК є два види пам’яті: загальна пам’ять, та регістрова пам’ять. В загальній пам’яті зберігаються інструкції програми та дані над якими оперують інструкції. В регістровий пам’яті зберігаються дані над якими виконуються інструкції. У реальних комп’ютерах регістрова пам’ять є малою за розмірами та швидкою, працює на швидкості ядра процесора, загальна пам’ять є великою за розміром, але набагато повільніша за ядро процесора. Регістрова пам’ять підтримує лише пряму адресацію, загальна пам’ять підтримує декілька типів адресації. У СК є 8 регістрів по 32 розряди, пам’ять складається з 65536 слів по 32 розряди. Отже СК є 32 розрядним комп’ютером. Він підтримує 8 інструкцій. У СК є спеціальний регістр – лічильник команд (programCounter), в якому зберігається адреса інструкції. За прийнятою домовленістю нульовий регістр завжди містить 0. Рис. 2.2.1. Функціональна схема спрощеного комп’ютера до модифікації 20 В регістрі instReg міститься код інструкції, яка виконується. При роботі з пам’яттю (MEMORY) використовуються два регістри – в memAddress зберігається адреса пам’яті, а в memData значення, яке читається/записується в пам’ять. Комп’ютер містить арифметико-логічний пристрій (ALU), який може виконувати 3 дії: додавання, логічне І-НЕ і перевірку на рівність. ALU керується двохрозрядним сигналом alu_op: alu_op Дія 00 + 01 І-НЕ 10 == ALU працює з двома операндами, один з них завжди є в регістрі aluOperand, інший береться з головної шини (main bus), результат зберігається в регістрі aluResult. 21 2.3. Функціональна схема комп’ютера після модифікації Після модифікації відбулись зміни : 1. Шина даних залишилась такою ж 2. Додано 8 додаткових операцій: 3 арифметичні операції (XADD, DIV, XIMUL); 3 логічні операції (SHR, ROR, OR); 2 операції керування (JMA, JMNLE). 3. Було додано прапорець CF – (Cery flag), а також команди BT, CMP, SBB, які працюють в парі з прапорцем. 4. Пам’ять зросла до 1048576. 5. Збільшилось число регістрів (з 8 до 32). Отже , СК є 40 розрядним комп’ютером. У СК є 32 регістрів по 40 розрядів. Пам’ять складається з 1048576 слів по 40 розрядів. Він підтримує 24 інструкцій, кожна з яких буде розписана нижче. За прийнятою домовленістю нульовий регістр завжди містить 0 (це не обмовлено апаратними вимогами проте асемблерна програма ніколи не має змінювати значення нльового регістра, який ініціалізуються 0 ). 22 2.4. Множина інструкцій комп’ютера № Код 2СУТНІСТЬ ІНСТРУКЦІЇ інструкції кове R-тип 1 add regA regB destReg 00000 Додає вміст регістру regA до вмісту regB, та зберігає в destReg 2 nand regA regB destReg 00001 Виконує логічне побітове І-НЕ вмісту regA з вмістом regB, та зберігає в destReg 3 div regA regB destReg 00010 Беззнакове ділення destReg=regA/regB 4 xadd regA regB destReg 00011 Додавання і обмін операндів місцями 5 shr regA regB destReg 00100 Логічний зсув вправо 6 or regA regB destReg 00101 Логічне побітове АБО 7 ror regA regB destReg 00110 Циклічний зсув вправо 8 sbb regA regB destReg 00111 Віднімання з переносом: destReg=regA-regBСF 9 XIMUL regA destReg regB 01000 Знакове множення і обмін операндів місцями destReg=regA*regB I-тип 10 lw regA regB offSet 01001 Завантажує regB з пам’яті. Адреса пам’яті формується додаванням offSet до вмісту regA. 11 sw regA regB offSet 01010 Зберігає вміст регістру regB в пам’ять. Адреса пам’яті формується додаванням offSet до вмісту regA. 12 beq regA regB offSet 01011 Якщо вміст регістрів regA та regB однаковий, виконується перехід на адресу програмний лічильник(ПЛ) + 1+ offSet, в ПЛ адреса поточної, тобто beq інструкції. 23 13 jma regA regB offSet 01100 Беззнакове більше if (regA> regB) PC=PC+1+offSet 14 jmnle regA regB offset 01101 Знакове не менше/рівно if (regA!<= regB) PC=PC+1+offSet J-тип 15 jalr regA regB 01110 Спочатку зберігає ПЛ+1 в regB, в ПЛ адреса поточної (jalr) інструкції. Виконує перехід на адресу, яка зберігається в regA. Якщо в якості regA regB задано один і той самий регістр, то спочатку в цей регістр запишеться ПЛ+1, а потім виконається перехід до ПЛ+1. 16 bt regA regB 01111 Копіює значення біта з regA по позиції regB в CF=regA[regB] 17 cmp regA regB 10000 Порівняти regA regB і встановити прапорець CF=1 якщо regA<regB O-тип 18 halt 10011 Збільшує значення ПЛ на 1, потім припиняє виконання, стимулятор має повідомляти, що виконано зупинку. 24 2.5. Опис форматів інструкцій розробленого комп’ютера Інструкції R-типу (add, nand, div, xadd, shr, or, ror, sbb, ximul): біти 34-30: код операції; біти 29-25: reg A; біти 24-20: reg B; біти 19-5: не використовуються; біти 4-0: destReg. 34-30 29-25 24-20 19-5 4-0 opcode regA regB unused destReg Рис.2.5.1. Інструкція R-типу після модифікації. I-тип інструкцій (lw, sw, beq, jma , jmnle): біти 34-30: код операції; біти 29-25: reg A; біти 24-20: reg B; біти 19-0: зміщення (20 біт, значення від -524288 до 524287). 34-30 29-25 24-20 19-0 opcode regA regB Offset Рис.2.5.2. Інструкція I-типу після модифікації. J-тип інструкцій (jalr, bt, cmp): біти 34-30: код операції; біти 29-25: reg A; біти 24-20: reg B; біти 19-0: не використовуються; 34-30 29-25 24-20 19-0 opcode regA regB Unused Рис.2.5.3. Інструкція J-типу після модифікації. 25 O-тип (halt): біти 34-30: код операції; біти 29-0: не використовуються. 34-30 29-0 opcode Unused Рис.2.5.4. Інструкція O-типу після модифікації. CF – використовується в командах CMP, BT, SBB. 26 2.6. Опис виконання кожного типу інструкцій в потактовому режимі. Інструкція DIV ;SHR; OR; ROR; regA regB destReg 1. 2. 3. 4. 5. 6. 7. memAddress <= programCounter memDate <= MEMORY[memAdress] programCounter++ instReg <= memDate aluOperand <= RegisterFile[29-25] aluResult <= aluOperand, operation, RegisterFile[24-20] RegisterFile[0-4] <= aluResult Інструкція XADD; XIMUL regA regB destReg 1. 2. 3. 4. 5. 6. 7. 8. 9. memAddress <= programCounter memDate <= MEMORY[memAdress] programCounter++ instReg <= memDate aluOperand <= RegisterFile[29-25] aluResult <= aluOperand, operation, RegisterFile[24-20] RegisterFile[29-25] <= RegisterFile[24-20] RegisterFile[24-20] <= aluOperand RegisterFile[0-4] <= aluResult Інструкція JMA; JMNLE RegA RegB offset 1. memAddress <= programCounter 2. memDate <= MEMORY[memAdress] 3. programCounter++ 4. instReg <= memDate 5. aluOperand <= RegisterFile[29-25] 6. aluResult <= aluOperand, operation, RegisterFile[24-20] 7. aluOperand <= aluResult 8. aluResult <= aluResult, operation, RegisterFile[19-0] 9. aluOperand <= aluResult 10.aluResult <= programCounter, operation, aluOperand 11.programCounter <= aluResult 27 Інструкція BT regA regB 1. memAddress <= programCounter 2. memDate <= MEMORY[memAdress] 3. programCounter++ 4. instReg <= memDate 5. aluOperand <= RegisterFile[23-20] 6. CF = aluOperand[RegisterFile[19-16] 28 3. Тестовий розділ В даному розділі розроблено тестові програми для перевірки правильності роботи створеного проекту. Арифметичні операції lw 0 lw 0 start div 1 xadd 1 ximul 1 end halt a .fill 10 b .fill 2 1 2 2 2 2 a b 3 4 5 Результат: 1. Виконується перша команда div (ділення). @@@ state: pc 3 memory: mem[ 0 ] 2148532230 mem[ 1 ] 2149580807 mem[ 2 ] 9699328003 mem[ 3 ] 8625586180 mem[ 4 ] 10773069829 mem[ 5 ] 6442450944 mem[ 6 ] 10 mem[ 7 ] 2 registers: reg[ 0 ] 0 reg[ 1 ] 10 reg[ 2 ] 2 reg[ 3 ] 5 2. Виконується друга команда xadd (додавання і обмін регістрами). @@@ state: pc 4 memory: mem[ 0 ] 2148532230 mem[ 1 ] 2149580807 mem[ 2 ] 9699328003 29 mem[ 3 ] 8625586180 mem[ 4 ] 10773069829 mem[ 5 ] 6442450944 mem[ 6 ] 10 mem[ 7 ] 2 registers: reg[ 0 ] 0 reg[ 1 ] 2 reg[ 2 ] 10 reg[ 3 ] 5 reg[ 4 ] 12 3. Виконується третя команда ximul (множення і обмін регістрами). @@@ state: pc 5 memory: mem[ 0 ] 2148532230 mem[ 1 ] 2149580807 mem[ 2 ] 9699328003 mem[ 3 ] 8625586180 mem[ 4 ] 10773069829 mem[ 5 ] 6442450944 mem[ 6 ] 10 mem[ 7 ] 2 registers: reg[ 0 ] 0 reg[ 1 ] 10 reg[ 2 ] 2 reg[ 3 ] 5 reg[ 4 ] 12 reg[ 5 ] 20 4. Кінцевий стан. machine halted @@@ total of 6 instructions executed state: final state of machine: pc 6 memory: 30 mem[ 0 ] 2148532230 reg[ 13 ] 0 mem[ 1 ] 2149580807 reg[ 14 ] 0 mem[ 2 ] 9699328003 reg[ 15 ] 0 mem[ 3 ] 8625586180 reg[ 16 ] 0 mem[ 4 ] 10773069829 reg[ 17 ] 0 mem[ 5 ] 6442450944 reg[ 18 ] 0 mem[ 6 ] 10 reg[ 19 ] 0 mem[ 7 ] 2 reg[ 20 ] 0 registers: reg[ 21 ] 0 reg[ 0 ] 0 reg[ 22 ] 0 reg[ 1 ] 10 reg[ 23 ] 0 reg[ 2 ] 2 reg[ 24 ] 0 reg[ 3 ] 5 reg[ 25 ] 0 reg[ 4 ] 12 reg[ 26 ] 0 reg[ 5 ] 20 reg[ 27 ] 0 reg[ 6 ] 0 reg[ 28 ] 0 reg[ 7 ] 0 reg[ 29 ] 0 reg[ 8 ] 0 reg[ 30 ] 0 reg[ 9 ] 0 reg[ 31 ] 0 reg[ 10 ] 0 CF = 0 reg[ 11 ] 0 end state reg[ 12 ] 0 Логічні операції lw 0 lw 0 start shr 1 or 1 ror 1 end halt a .fill 7 b .fill 2 1 2 2 2 2 a b 4 5 6 Результат: У регістрах 4,5,6 зберігається результат виконання команд shr, or та ror відповідно. final state of machine: mem[ 0 ] 2148532230 @@@ mem[ 1 ] 2149580807 state: mem[ 2 ] 12920553476 pc 6 mem[ 3 ] 11846811653 memory: mem[ 4 ] 13994295302 31 mem[ 5 ] 6442450944 reg[ 15 ] 0 mem[ 6 ] 7 reg[ 16 ] 0 mem[ 7 ] 2 reg[ 17 ] 0 registers: reg[ 18 ] 0 reg[ 0 ] 0 reg[ 19 ] 0 reg[ 1 ] 7 reg[ 20 ] 0 reg[ 2 ] 2 reg[ 21 ] 0 reg[ 3 ] 0 reg[ 22 ] 0 reg[ 4 ] 1 reg[ 23 ] 0 reg[ 5 ] 7 reg[ 24 ] 0 reg[ 6 ] -1073741823 reg[ 25 ] 0 reg[ 7 ] 0 reg[ 26 ] 0 reg[ 8 ] 0 reg[ 27 ] 0 reg[ 9 ] 0 reg[ 28 ] 0 reg[ 10 ] 0 reg[ 29 ] 0 reg[ 11 ] 0 reg[ 30 ] 0 reg[ 12 ] 0 reg[ 31 ] 0 reg[ 13 ] 0 CF = 0 reg[ 14 ] 0 end state Операції керування lw lw start jma add add add add jmnle add add add add end halt a .fill 10 b .fill 5 0 0 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 a b 2 4 5 6 7 2 8 9 10 11 Результат: final state of machine: memory: @@@ mem[ 0 ] 2148532237 state: mem[ 1 ] 2149580814 pc 13 mem[ 2 ] 15068037122 32 mem[ 3 ] 35651588 reg[ 11 ] 15 mem[ 4 ] 35651589 reg[ 12 ] 0 mem[ 5 ] 35651590 reg[ 13 ] 0 mem[ 6 ] 35651591 reg[ 14 ] 0 mem[ 7 ] 16141778946 reg[ 15 ] 0 mem[ 8 ] 35651592 reg[ 16 ] 0 mem[ 9 ] 35651593 reg[ 17 ] 0 mem[ 10 ]35651594 reg[ 18 ] 0 mem[ 11 ]35651595 reg[ 19 ] 0 mem[ 12 ] 6442450944 reg[ 20 ] 0 mem[ 13 ] 10 reg[ 21 ] 0 mem[ 14 ] 5 reg[ 22 ] 0 registers: reg[ 23 ] 0 reg[ 0 ] 0 reg[ 24 ] 0 reg[ 1 ] 10 reg[ 25 ] 0 reg[ 2 ] 5 reg[ 26 ] 0 reg[ 3 ] 0 reg[ 27 ] 0 reg[ 4 ] 0 reg[ 28 ] 0 reg[ 5 ] 0 reg[ 29 ] 0 reg[ 6 ] 15 reg[ 30 ] 0 reg[ 7 ] 15 reg[ 31 ] 0 reg[ 8 ] 0 CF = 0 reg[ 9 ] 0 end state reg[ 10 ] 15 Операції, які працюють з прапорцями start lw 0 lw 0 bt cmp sbb 1 2 1 1 1 a b 1 2 2 3 end halt a .fill 4 b .fill 9 Результат: final state of machine: pc 6 @@@ memory: state: mem[ 0 ] 2148532230 33 mem[ 1 ] 2149580807 reg[ 13 ] 0 mem[ 2 ] 17214472192 reg[ 14 ] 0 mem[ 3 ] 18289262592 reg[ 15 ] 0 mem[ 4 ] 19363004419 reg[ 16 ] 0 mem[ 5 ] 6442450944 reg[ 17 ] 0 mem[ 6 ] 4 reg[ 18 ] 0 mem[ 7 ] 9 reg[ 19 ] 0 registers: reg[ 20 ] 0 reg[ 0 ] 0 reg[ 21 ] 0 reg[ 1 ] 4 reg[ 22 ] 0 reg[ 2 ] 9 reg[ 23 ] 0 reg[ 3 ] -6 reg[ 24 ] 0 reg[ 4 ] 0 reg[ 25 ] 0 reg[ 5 ] 0 reg[ 26 ] 0 reg[ 6 ] 0 reg[ 27 ] 0 reg[ 7 ] 0 reg[ 28 ] 0 reg[ 8 ] 0 reg[ 29 ] 0 reg[ 9 ] 0 reg[ 30 ] 0 reg[ 10 ] 0 reg[ 31 ] 0 reg[ 11 ] 0 CF = 1 reg[ 12 ] 0 end state Операції, які працюють з додатковою адресацією lw 0 1 lw 0 2 lw 0 3 lw 0 4 lw 0 5 lw 0 6 lw 0 7 lw 0 8 lw 0 9 lw 0 10 divn 1 3 addn 1 3 subn 1 3 halt x1 .fill x2 .fill x3 .fill x4 .fill x5 .fill x6 .fill x1 x2 x3 x4 x5 x6 x7 x8 x9 x10 5 7 9 2 20 4 5 6 0 34 x7 .fill x8 .fill x9 .fill x10 .fill 8 0 10 0 Результат: Спочатку виконується команда divn 1 3 5. Із регістра 1 отримує значення 2, яке у свою чергу є номером регістра, який зберігає значення 20. Отже, отримуємо значення 20. Аналогічно отримуємо інше значення, яке дорівнює 5. Результат необхідно записати у регістр 6. Після цієї операції туди буде записано значення 20/5 = 4. За цим же ж алгоритмом у регістр 8 буде записано результат 20+5 = 25, у регістр 10 результат 20-5 = 15. final state of machine: mem[ 20 ] 8 mem[ 21 ] 0 @@@ mem[ 22 ] 10 state: mem[ 23 ] 0 pc 14 registers: memory: reg[ 0 ] 0 mem[ 0 ] 2148532238 reg[ 1 ] 2 mem[ 1 ] 2149580815 reg[ 2 ] 20 mem[ 2 ] 2150629392 reg[ 3 ] 4 mem[ 3 ] 2151677969 reg[ 4 ] 5 mem[ 4 ] 2152726546 reg[ 5 ] 6 mem[ 5 ] 2153775123 reg[ 6 ] 4 mem[ 6 ] 2154823700 reg[ 7 ] 8 mem[ 7 ] 2155872277 reg[ 8 ] 25 mem[ 8 ] 2156920854 reg[ 9 ] 10 mem[ 9 ] 2157969431 reg[ 10 ] 15 mem[ 10 ] 2157969431 reg[ 11 ] 0 mem[ 11 ] 2157969431 reg[ 12 ] 0 mem[ 12 ] 2157969431 reg[ 13 ] 0 mem[ 13 ] 6442450944 reg[ 14 ] 0 mem[ 14 ] 2 reg[ 15 ] 0 mem[ 15 ] 20 reg[ 16 ] 0 mem[ 16 ] 4 reg[ 17 ] 0 mem[ 17 ] 5 reg[ 18 ] 0 mem[ 18 ] 6 reg[ 19 ] 0 mem[ 19 ] 0 reg[ 20 ] 0 35 reg[ 21 ] 0 reg[ 28 ] 0 reg[ 22 ] 0 reg[ 29 ] 0 reg[ 23 ] 0 reg[ 30 ] 0 reg[ 24 ] 0 reg[ 31 ] 0 reg[ 25 ] 0 CF = 0 reg[ 26 ] 0 end state reg[ 27 ] 0 36 Використана література 1. Мельник А. О. Архітектура комп’ютера. Наукове видання. – Луцьк: Волинська обласна друкарня, 2008. – 470 с. 2. Методичні вказівки до курсової роботи на тему “Проектування комп’ютера”. Мельник А.О., Троценко В.В. 3. Жмакин А. П. Архитектура ЭВМ. – СПб.: БХВ-Петербург, 2006. — 320 с: ил. 4. Таненбаум Э. Архитектура компьютера. 5-е изд. (+CD). — СПб.: Питер, 2007. — 844 с: ил. 5. Patterson D., and Hennessy J. Computer Architecture. A quantitative Approach. Second Edition. - Morgan Kaufmann Publishers, Inc., San Francisco, California, 1996. - 760 p. 37 Висновок В ході виконання курсової роботи був розроблений асемблер, який дозволяє переводити всі задані варіантом команди у машинні інструкції. Даною програмою виконується безліч перевірок на відповідність правилам синтаксису і граматики. Також була розроблена програма симулятора, що дозволяє дешифрувати всі машинні інструкції і виконати потрібну операцію, а також створює звіт потактового виконання програми з виводом стану пам’яті і регістрів. Симулятор відповідає всім поставленим вимогам, робить перевірки на помилки і в разі їх виникнення виводить відповідне повідомлення. Для тестування роботи обох програм створено систему тестів, що спрямована на виявлення помилок в створенні машинних інструкцій, розшифруванні машинних інструкцій, а також у виконанні команд. 38 Додатки ASOL #define _CRT_SECURE_NO_WARNINGS #include <stdlib.h> #include <stdio.h> #include <string.h> #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define MAXNUMLABELS 65536 MAXLABELLENGTH 7 MAXLINELENGTH 1000 ADD 0 NAND 1 LW 2 SW 3 BEQ 4 JALR 5 HALT 6 NOOP 7 XADD 8 DIV 9 XIMUL 10 OR 11 SHR 12 ROR 13 JMA 14 JMNLE 15 BT 16 CMP 17 SBB 18 DIVN 19 ADDN 20 SUBN 21 long long int readAndParse(FILE*, char*, char*, char*, char*, char*); long long int translateSymbol(char labelArray[MAXNUMLABELS][MAXLABELLENGTH], long long int labelAddress[MAXNUMLABELS], long long int, char*); int isNumber(char*); void testRegArg(char*); void testAddrArg(char*); void testArg(char*); int main(int argc, char* argv[]) { char* inFileString, * outFileString; FILE* inFilePtr, * outFilePtr; long long int address; char label[MAXLINELENGTH], opcode[MAXLINELENGTH], arg0[MAXLINELENGTH], arg1[MAXLINELENGTH], arg2[MAXLINELENGTH], argTmp[MAXLINELENGTH]; int i; long long int numLabels = 0; long long int num; long long int addressField; long long int constant; char labelArray[MAXNUMLABELS][MAXLABELLENGTH]; long long int labelAddress[MAXNUMLABELS]; if (argc != 3) { printf("error: usage: %s <assembly-code-file> <machine-code-file>\n", argv[0]); 39 exit(1); } inFileString = argv[1]; outFileString = argv[2]; inFilePtr = fopen(inFileString, "r"); if (inFilePtr == NULL) { printf("error in opening %s\n", inFileString); exit(1); } outFilePtr = fopen(outFileString, "w"); if (outFilePtr == NULL) { printf("error in opening %s\n", outFileString); exit(1); } /* map symbols to addresses */ /* assume address start at 0 */ for (address = 0; readAndParse(inFilePtr, label, opcode, arg0, arg1, arg2); address++) { /* check for illegal opcode */ if (strcmp(opcode, "add") && strcmp(opcode, "nand") && strcmp(opcode, "lw") && strcmp(opcode, "sw") && strcmp(opcode, "beq") && strcmp(opcode, "jalr") && strcmp(opcode, "halt") && strcmp(opcode, "noop") && strcmp(opcode, ".fill") && strcmp(opcode, "xadd") && strcmp(opcode, "div") && strcmp(opcode, "ximul") && strcmp(opcode, "or") && strcmp(opcode, "shr") && strcmp(opcode, "ror") && strcmp(opcode, "jma") && strcmp(opcode, "jmnle") && strcmp(opcode, "bt") && strcmp(opcode, "cmp") && strcmp(opcode, "sbb") && strcmp(opcode, "divn") && strcmp(opcode, "addn") && strcmp(opcode, "subn")) { printf("error: unrecognized opcode %s at address %lli\n", opcode, address); exit(1); } /* check register fields */ if (!strcmp(opcode, "add") || !strcmp(opcode, "nand") || !strcmp(opcode, "lw") || !strcmp(opcode, "sw") || !strcmp(opcode, "beq") || !strcmp(opcode, "jalr") || !strcmp(opcode, "div") || !strcmp(opcode, !strcmp(opcode, "shr") || !strcmp(opcode, "jma") || !strcmp(opcode, "jmnle") || !strcmp(opcode, !strcmp(opcode, "divn") || !strcmp(opcode, testRegArg(arg0); testRegArg(arg1); } "xadd") || !strcmp(opcode, "ximul") || "ror") || !strcmp(opcode, "or") || !strcmp(opcode, "bt") || !strcmp(opcode, "cmp") || !strcmp(opcode, "sbb") || !strcmp(opcode, "addn") || "subn")) { if (!strcmp(opcode, "add") || !strcmp(opcode, "nand") || !strcmp(opcode, "div") || !strcmp(opcode, "xadd") || !strcmp(opcode, "ximul") || !strcmp(opcode, "shr") 40 || !strcmp(opcode, "ror") || !strcmp(opcode, "or") || !strcmp(opcode, "sbb") || !strcmp(opcode, "addn") || !strcmp(opcode, "divn") || !strcmp(opcode, "subn")) { testRegArg(arg2); } /* check addressField */ if (!strcmp(opcode, "lw") || !strcmp(opcode, "sw") || !strcmp(opcode, "beq") || !strcmp(opcode, "jma") || !strcmp(opcode, "jmnle")) { testAddrArg(arg2); } if (!strcmp(opcode, ".fill")) { testAddrArg(arg0); } /* check for enough arguments */ if ((strcmp(opcode, "halt") && strcmp(opcode, "noop") && strcmp(opcode, ".fill") && strcmp(opcode, "jalr") && strcmp(opcode, "bt") && strcmp(opcode, "cmp") && arg2[0] == '\0') || (!strcmp(opcode, "jalr") && !strcmp(opcode, "bt") && !strcmp(opcode, "cmp") && arg1[0] == '\0') || (!strcmp(opcode, ".fill") && !strcmp(opcode, "inc") && arg0[0] == '\0')) { printf("error at address %lli: not enough arguments\n", address); exit(2); } if (label[0] != '\0') { /* check for labels that are too long */ if (strlen(label) >= MAXLABELLENGTH) { printf("label too long\n"); exit(2); } /* make sure label starts with letter */ if (!sscanf(label, "%[a-zA-Z]", argTmp)) { printf("label doesn't start with letter\n"); exit(2); } /* make sure label consists of only letters and numbers */ sscanf(label, "%[a-zA-Z0-9]", argTmp); if (strcmp(argTmp, label)) { printf("label has character other than letters and numbers\n"); exit(2); } /* look for duplicate label */ for (i = 0; i < numLabels; i++) { if (!strcmp(label, labelArray[i])) { printf("error: duplicate label %s at address %lli\n", label, address); exit(1); } } /* see if there are too many labels */ if (numLabels >= MAXNUMLABELS) { printf("error: too many labels (label=%s)\n", label); exit(2); } 41 strcpy(labelArray[numLabels], label); labelAddress[numLabels++] = address; } } for (i = 0; i < numLabels; i++) { /* printf("%s = %d\n", labelArray[i], labelAddress[i]); */ } /* now do second pass (print machine code, with symbols filled in as addresses) */ rewind(inFilePtr); for (address = 0; readAndParse(inFilePtr, label, opcode, arg0, arg1, arg2); address++) { if (!strcmp(opcode, "add")) { num = ((long long int) ADD << 30) | (atoll(arg0) << 25) | (atoll(arg1) << 20) | atoll(arg2); } else if (!strcmp(opcode, "nand")) { num = ((long long int) NAND << 30) | (atoll(arg0) << 25) | (atoll(arg1) << 20) | atoll(arg2); } else if (!strcmp(opcode, "div")) { num = ((long long int) DIV << 30) | (atoll(arg0) << 25) | (atoll(arg1) << 20) | atoll(arg2); } (atoll(arg1) (atoll(arg1) (atoll(arg1) (atoll(arg1) else if (!strcmp(opcode, "xadd")) { num = ((long long int) XADD << 30) | (atoll(arg0) << 25) | << 20) | atoll(arg2); } else if (!strcmp(opcode, "ximul")) { num = ((long long int) XIMUL << 30) | (atoll(arg0) << 25) | << 20) | atoll(arg2); } else if (!strcmp(opcode, "shr")) { num = ((long long int) SHR << 30) | (atoll(arg0) << 25) | << 20) | atoll(arg2); } else if (!strcmp(opcode, "ror")) { num = ((long long int) ROR << 30) | (atoll(arg0) << 25) | << 20) | atoll(arg2); } else if (!strcmp(opcode, "or")) { num = ((long long int) OR << 30) | (atoll(arg0) << 25) | (atoll(arg1) << 20) | atoll(arg2); } else if (!strcmp(opcode, "sbb")) { num = ((long long int) SBB << 30) | (atoll(arg0) << 25) | (atoll(arg1) << 20) | atoll(arg2); } else if (!strcmp(opcode, "jalr")) { num = ((long long int) JALR << 30) | (atoll(arg0) << 25) | (atoll(arg1) << 20); 42 } else if (!strcmp(opcode, "bt")) { num = ((long long int) BT << 30) | (atoll(arg0) << 25) | (atoll(arg1) << 20); } else if (!strcmp(opcode, "cmp")) { num = ((long long int)CMP << 30) | (atoll(arg0) << 25) | (atoll(arg1) << 20); } else if (!strcmp(opcode, num = ((long long } else if (!strcmp(opcode, num = ((long long } "halt")) { int) HALT << 30); "noop")) { int) NOOP << 30); else if (!strcmp(opcode, "lw") || !strcmp(opcode, "sw") || !strcmp(opcode, "beq") || !strcmp(opcode, "jma") || !strcmp(opcode, "jmnle")) { /* if arg2 is symbolic, then translate into an address */ if (!isNumber(arg2)) { addressField = translateSymbol(labelArray, labelAddress, numLabels, arg2); /* printf("%s being translated into %d\n", arg2, addressField); */ if (!strcmp(opcode, "beq")) { addressField = addressField - address - 1; } } else { addressField = atoll(arg2); } if (addressField < -524288 || addressField > 524287) { printf("error: offset %lli out of range\n", addressField); exit(1); } /* truncate the offset field, in case it's negative */ addressField = addressField & 0xFFFFF; if (!strcmp(opcode, "beq")) { num = ((long long int)BEQ << 30) | (atoll(arg0) << 25) | (atoll(arg1) << 20) | addressField; } else if (!strcmp(opcode, "lw")) { num = ((long long int) LW << 30) | (atoll(arg0) << 25) | (atoll(arg1) << 20) | addressField; } else if (!strcmp(opcode, "sw")) { num = ((long long int) SW << 30) | (atoll(arg0) << 25) | (atoll(arg1) << 20) | addressField; } else if (!strcmp(opcode, "jma")) { num = ((long long int) JMA << 30) | (atoll(arg0) << 25) | (atoll(arg1) << 20) | addressField; } else if (!strcmp(opcode, "jmnle")) { num = ((long long int) JMNLE << 30) | (atoll(arg0) << 25) | (atoll(arg1) << 20) | addressField; 43 } } else if (!strcmp(opcode, ".fill")) { if (!isNumber(arg0)) { num = translateSymbol(labelArray, labelAddress, numLabels, arg0); } else { num = atoll(arg0); if (num >= 0) { num = num & 0xFFFFFFFFFF; } else { num = num & 0xFFFFFFFFFF; num = num | 0xFFFFFF0000000000; } } } /* printf("(address %d): %d (hex 0x%x)\n", address, num, num); */ fprintf(outFilePtr, "%lli\n", num); } exit(0); } long long int readAndParse(FILE* inFilePtr, char* label, char* opcode, char* arg0, char* arg1, char* arg2) { char line[MAXLINELENGTH]; char* ptr = line; label[0] = opcode[0] = arg0[0] = arg1[0] = arg2[0] = '\0'; if (fgets(line, MAXLINELENGTH, inFilePtr) == NULL) { /* reached end of file */ return(0); } /* check for line too long */ if (strlen(line) == MAXLINELENGTH - 1) { printf("error: line too long\n"); exit(1); } ptr = line; if (sscanf(ptr, "%[^\t\n ]", label)) { ptr += strlen(label); } sscanf(ptr, "%*[\t\n\r ]%[^\t\n\r ]%*[\t\n\r ]%[^\t\n\r ]%*[\t\n\r ]%[^\t\n\r ]%*[\t\n\r ]%[^\t\n\r ]", opcode, arg0, arg1, arg2); return(1); } long long int translateSymbol(char labelArray[MAXNUMLABELS][MAXLABELLENGTH], long long int labelAddress[MAXNUMLABELS], long long int numLabels, char* symbol) 44 { int i; for (i = 0; i < numLabels && strcmp(symbol, labelArray[i]); i++) { } if (i >= numLabels) { printf("error: missing label %s\n", symbol); exit(1); } return(labelAddress[i]); } int isNumber(char* string) { int i; return((sscanf(string, "%d", &i)) == 1); } void testRegArg(char* arg) { long long int num; char c; if (atoll(arg) < 0 || atoll(arg) > 31) { printf("error: register out of range\n"); exit(2); } if (sscanf(arg, "%lli%c", &num, &c) != 1) { printf("bad character in register argument\n"); exit(2); } } void testAddrArg(char* arg) { long long int num; char c; /* test numeric addressField */ if (isNumber(arg)) { if (sscanf(arg, "%lli%c", &num, &c) != 1) { printf("bad character in addressField\n"); exit(2); } } } void testArg(char* arg) { long long int num; char c; if (atoll(arg) < 0) { atoll(arg) == atoll(arg) && 0xFFFFF; } /* test numeric addressField */ 45 if (isNumber(arg)) { if (sscanf(arg, "%lli%c", &num, &c) != 1) { printf("bad character in constant\n"); exit(2); } } } SSOLL #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <string.h> #define NUMMEMORY 65536 /* maximum number of words in memory */ #define NUMREGS 32 /* number of machine registers */ #define MAXLINELENGTH 1000 #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define ADD 0 NAND 1 LW 2 SW 3 BEQ 4 JALR 5 HALT 6 NOOP 7 XADD 8 DIV 9 XIMUL 10 OR 11 SHR 12 ROR 13 JMA 14 JMNLE 15 BT 16 CMP 17 SBB 18 DIVN 19 ADDN 20 SUBN 21 typedef struct stateStruct { long long int pc; long long int mem[NUMMEMORY]; long long int reg[NUMREGS]; long long int numMemory; long long int CF; } stateType; void printState(stateType*); void run(stateType*); int convertNum(int); int lr(int number, int shatal) { for (int i = 0; i < shatal; i++) { if (number < 0) 46 { number *= -1; number = number >> 1; } else { number = number >> 1; } } return number; } int loopr(int number, int shatal) { int l_n; for (int i = 0; i < shatal; i++) { if (number % 2 == 1) { if (number > 0) { number = lr(number, 1); number = number | 2147483648; } else { number = lr(number, 1); } } else { if (number > 0) { number = lr(number, 1); } else { number = number >> 1; printf("hhhh"); } } } return number; } int main(int argc, char* argv[]) { int i; char line[MAXLINELENGTH]; stateType* state = new stateType; FILE* filePtr; if (argc != 2) { printf("error: usage: %s <machine-code file>\n", argv[0]); exit(1); } /* initialize memories and registers */ for (i = 0; i < NUMMEMORY; i++) { 47 state->mem[i] = 0; } for (i = 0; i < NUMREGS; i++) { state->reg[i] = 0; } state->pc = 0; state->CF = 0; filePtr = fopen(argv[1], "r"); if (filePtr == NULL) { printf("error: can't open file %s\n", argv[1]); perror("fopen"); exit(1); } for (state->numMemory = 0; fgets(line, MAXLINELENGTH, filePtr) != NULL; state->numMemory++) { if (state->numMemory >= NUMMEMORY) { printf("exceeded memory size\n"); exit(1); } if (sscanf(line, "%lli", state->mem + state->numMemory) != 1) { printf("error in reading address %lli\n", state->numMemory); exit(1); } printf("memory[%lli]=%lld\n", state->numMemory, state->mem[state>numMemory]); } printf("\n"); /* run never returns */ run(state); return(0); } void run(stateType* state) { long long int arg0, arg1, arg2, addressField; int constant; long long int instructions = 0; long long int opcode; long long int maxMem = -1; for (; 1; instructions++) { printState(state); if (state->pc < 0 || state->pc >= NUMMEMORY) { printf("pc went out of the memory range\n"); exit(1); } maxMem = (state->pc > maxMem) ? state->pc : maxMem; /* this is to make the following code easier to read */ opcode = state->mem[state->pc] >> 30; arg0 = (state->mem[state->pc] >> 25) & 0x1F; arg1 = (state->mem[state->pc] >> 20) & 0x1F; arg2 = state->mem[state->pc] & 0x1F; /* only for add, nand */ constant = state->mem[state->pc] & 0xFFFFF; 48 addressField = convertNum(state->mem[state->pc] & 0xFFFFF); /* for beq, lw, sw */ state->pc++; if (opcode == ADD) { state->reg[arg2] = state->reg[arg0] + state->reg[arg1]; if (state->reg[arg2] > 1099511627775) { state->reg[arg2] = 0; } } else if (opcode == NAND) { state->reg[arg2] = ~(state->reg[arg0] & state->reg[arg1]); } else if (opcode == DIVN) { state->reg[state->reg[arg2]] = state->reg[state->reg[arg0]] / state>reg[state->reg[arg1]]; } else if (opcode == ADDN) { state->reg[state->reg[arg2]] = state->reg[state->reg[arg0]] + state>reg[state->reg[arg1]]; } else if (opcode == SUBN) { state->reg[state->reg[arg2]] = state->reg[state->reg[arg0]] - state>reg[state->reg[arg1]]; } else if (opcode == XADD) { state->reg[arg2] = state->reg[arg0] + state->reg[arg1]; long long int a = 0; a = state->reg[arg0]; state->reg[arg0] = state->reg[arg1]; state->reg[arg1] = a; } else if (opcode == XIMUL) { state->reg[arg2] = state->reg[arg0] + state->reg[arg1]; long long int a = 0; a = state->reg[arg0]; state->reg[arg0] = state->reg[arg1]; state->reg[arg1] = a; } else if (opcode == DIV) { long long int a; long long int b; if (state->reg[arg0] < 0) { a = (state->reg[arg0] ^ 0xFFFFFFFFFFFFFFFF) + 1; } else { a = state->reg[arg0]; } if (state->reg[arg1] < 0) { b = (state->reg[arg1] ^ 0xFFFFFFFFFFFFFFFF) + 1; } else { b = state->reg[arg1]; } state->reg[arg2] = a / b; } else if (opcode == OR) { state->reg[arg2] = state->reg[arg0] | state->reg[arg1]; } else if (opcode == SHR) { state->reg[arg2] = lr(state->reg[arg0], state->reg[arg1]); } else if (opcode == ROR) { state->reg[arg2] = loopr(state->reg[arg0], state->reg[arg1]); } else if (opcode == LW) { 49 if ((state->reg[arg0]) + addressField < 0 || (state->reg[arg0]) + addressField >= NUMMEMORY) { printf("address out of bounds\n"); exit(1); } state->reg[arg1] = state->mem[(state->reg[arg0]) + addressField]; state->reg[arg1] = state->reg[arg1]; if ((state->reg[arg0]) + addressField > maxMem) { maxMem = (state->reg[arg0]) + addressField; } } else if (opcode == SW) { if (state->reg[arg0] + addressField < 0 || state->reg[arg0] + addressField >= NUMMEMORY) { printf("address out of bounds\n"); exit(1); } state->mem[state->reg[arg0] + addressField] = state->reg[arg1]; if (state->reg[arg0] + addressField > maxMem) { maxMem = state->reg[arg0] + addressField; } } else if (opcode == BEQ) { if (state->reg[arg0] == state->reg[arg1]) { state->pc += addressField; } } else if (opcode == JMA) { if ((unsigned int)state->reg[arg0] > (unsigned int)state->reg[arg1]) { state->pc += addressField; } } else if (opcode == JMNLE) { if (state->reg[arg0] > state->reg[arg1]) { state->pc += addressField; } } else if (opcode == BT) { long long int temp = state->reg[arg0]; int i; for (i = 0; i < state->reg[arg1]; i++) temp = temp >> 1; temp = temp & 0x000000001; state->CF = temp; } else if (opcode == CMP) { if (state->reg[arg0] < state->reg[arg1]) { state->CF = 1; } else state->CF = 0; } else if (opcode == SBB) { state->reg[arg2] = state->reg[arg0] - state->reg[arg1] - state->CF; } else if (opcode == JALR) { state->reg[arg1] = state->pc; if (arg0 != 0) state->pc = state->reg[arg0]; else state->pc = 0; } else if (opcode == NOOP) { 50 } else if (opcode == HALT) { printf("machine halted\n"); printf("total of %lli instructions executed\n", instructions + 1); printf("final state of machine:\n"); printState(state); exit(0); } else { printf("error: illegal opcode 0x%lli\n", opcode); exit(1); } state->reg[0] = 0; } } void printState(stateType* statePtr) { int i; printf("\n@@@\nstate:\n"); printf("\tpc %lli\n", statePtr->pc); printf("\tmemory:\n"); for (i = 0; i < statePtr->numMemory; i++) { printf("\t\tmem[ %d ] %lld\n", i, statePtr->mem[i]); } printf("\tregisters:\n"); for (i = 0; i < NUMREGS; i++) { printf("\t\treg[ %d ] %lli\n", i, statePtr->reg[i]); } //printf("Index Register: %d\n", statePtr->inReg); printf("CF = %lli\n", statePtr->CF); printf("end state\n"); } int convertNum(int num) { /* convert a 16-bit number into a 32-bit Sun integer */ if (num & (1 << 19)) { num -= (1 << 20); } return(num); } 51