Проектування комп`ютера

СОДЕРЖАНИЕ: МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ НАЦІОНАЛЬНИЙ УНІВЕРСИТЕТ “ЛЬВІВСЬКА ПОЛІТЕХНІКА” КАФЕДРА ЕЛЕКТРОННІ ОБЧИСЛЮВАЛЬНІ МАШИНИ Пояснювальна записка

МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ

НАЦІОНАЛЬНИЙ УНІВЕРСИТЕТ “ЛЬВІВСЬКА ПОЛІТЕХНІКА”

КАФЕДРА ЕЛЕКТРОННІ ОБЧИСЛЮВАЛЬНІ МАШИНИ

Пояснювальна записка

до курсової роботи з дисципліни

Архітектура комп’ютерів на тему:

Проектування комп’ютера

Виконав:

ст. гр. КІ-34

Тітко М. І.

Прийняла:

ст. викл.

Ногаль М. В.

Львів 2010

Анотація

Курсовий проект з дисципліни Архітектура комп’ютера являє собою підсумок у вивченні предмету Архітектура комп’ютерів ч.1. Протягом його виконання необхідно засвоїти знання про принципи дії та архітектуру прототипних варіантів CISC - комп’ютера. Також під час виконання курсової роботи необхідно зрозуміти та опанувати інструкції асемблерної мови і принцип трансляції асемблерної програми у машинний код.

Даний курсовий проект складається з трьох частин:

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

- Розробка поведінкового симулятора результуючого машинного коду.

- Розробка тестової програми на асемблерній мові.

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

Зміст

Вступ

Система команд

Способи адресації

Вихідні дані на проектування

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

Перевірка правильності роботи реалізованих команд у прикладах

Висновок

Література

Додатки

Вступ

Архітектурні принципи

В ході виконання даного курсового проекту студент має ознайомитись та опанувати архітектуру CISC - комп’ютера. Приведемо основні принципи даної архітектури, які запропонував Джон фон Нейман:

1. Інформація кодується в двійковому представленні.

2. Інформація в комп’ютері ділиться на команди і дані.

3. Різнотипні за змістом слова розрізняються за способом застосування, а не по способу кодування.

4. Слова інформації розміщаються в комірках пам’яті та ідентифікуються номерами комірок - адресами слів.

5. Пам’ять є лінійною.

6. Пам’ять має довільну адресацію.

7. Команди і дані зберігаються в одній пам’яті.

8. Алгоритми представляються у вигляді послідовності керуючих слів, як називаються командами. Команда визначається найменуванням операції та слів інформації, які в ній приймають участь. Алгоритм записаний у вигляді послідовності команд, називається програмою.

9. Весь набір виконуваних комп’ютером команд називається системою команд комп’ютера.

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

Для виконання задачі на комп’ютері необхідно:

- забезпечити вибірку команди програми із його пам’яті в заданій послідовності, організувати звернення до неї за відповідними адресами;

- забезпечити розпізнавання типів виконуваних операцій;

- організувати звернення до пам’яті за відповідними адресами для вибірки необхідних для виконання кожної команди даних;

- організувати виконання над даними операцій відповідно до вказівок команд;

- запам’ятати результат обчислень.

Компютер виконує кожну команду як послідовність простих операцій:

1. Вибірка чергової команди із основної памяті.

2. Визначення типу вибраної команди, тобто її дешифрування.

3. Визначення адрес даних, необхідних для виконання цієї команди.

4. Виконання операцій пересилання даних (зчитування даних із памяті в регістри процесора).

5. Виконання операції відповідно до її коду в полі коду операції команди.

6. Визначення адрес, за якими запамятовуються результати.

7. Запамятовування результатів.

8. Підготовка до виконання наступної команди, тобто обчислення її адреси.

Для процесора компютера із складною системою команд характерні наступні

особливості:

- виконання команди за багато тактів, оскільки для цього потрібно здійснити багаторазові операції звернення до основної памяті та до програмно-доступних регістрів процесора;

- орієнтація АЛП на виконання великої кількості операцій, що повязано з розширеним складом системи команд;

- складна система розпізнавання команди, що повязано з великою кількістю методів адресації та великою кількістю форматів команд різної розрядності;

- програмне дешифрування команд з метою зменшення затрат обладнання;

- складна організація конвеєризації виконання команд, що повязано, в першу чергу, з різнотипністю їх виконання;

- орієнтація структури на виконання команд типу регістр-память та память-память.

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

Структура регістрової (надоперативної) памяті процесора складається з регістрів спеціального та зального призначення. До регістрів спеціального призначення належать:

- регістри адреси (РгА);

- регістри команд (РгК);

- програмний лічильник (ПЛ)

- регістри даних (РгД).

РгА зберігає адресу даного або команди при зверненні до основної памяті. РгД зберігає операнд при його запису або зчитуванні з основної памяті. В ролі операнда може бути дане, команда або адреса. РгК зберігає команду після її зчитування з основної памяті. ПЛ підраховує команди та зберігає адресу поточної команди. Компютер з архітектурою Джона фон Неймана має один програмний лічильник.

Більшість компютерів мають в складі процесора тригери для зберігання бітів стану процесора, або, як їх іще називають, прапорців. Кожен прапорець має спеціальне призначення. Частина прапорців вказує на результати арифметичних і логічних операцій: додатній результат (Р), відємний результат (N), нульовий результат (Z), перенос (С), арифметичне переповнення (V), і т.д. В системі команд компютера є команди, які вказують процесору коли встановити чи скинути ці тригери. Інша частина прапорців вказує режими захисту памяті. Існують також прапорці, які вказують пріоритети виконуваних програм. В деяких процесорах додаткові тригери служать для зберігання кодів умов, формуючи регістр кодів умов. Взяті разом описані прапорці формують слово стану програми (ССП), а відповідні тригери - регістр ССП. Регістри загального призначення (РЗП) є програмно доступними. Зазвичай їх називають регістровим файлом. Вони можуть використовуватись програмістом в якості регістрів для зберігання даних.

Система команд

Різноманітність типів даних, форм представлення та опрацювання, необхідні дії для обробки та керування ходом виконання обчислень призводить до необхідності використання різноманітних команд - набора команд.

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

Система команд характеризується трьома аспектами:

- формат,

- способи адресації,

- система операцій.

Форматом команди - є довжина команди, кількість, розмір, положення, призначення та спосіб кодування полів. Команди мають включати наступні види інформації:

- тип операції, яку необхідно реалізувати в даній команді (поле команду операції - КОП);

- місце в пам’яті звідки треба взяти перший операнд (А1);

- місце в пам’яті звідки треба взяти другий операнд (А2);

- місце в пам’яті куди треба помістити результат (А3).

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

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

КОП займає k розрядів. Ним може бути закодовано до N = 2k різних операцій. Кількість двійкових розрядів, які відводяться під код операції, вибирається таким чином, щоб ними можна було закодувати всі виконувані в даному компютері операції. Якщо деякий компютер може виконувати Nc різних операцій, то мінімальна розрядність поля коду операції k визначається наступним чином: k = [log Nc ], де вираз в дужках означає заокруглення до більшого цілого.

Поле адреси (адресна частина) займає m розрядів. В ньому знаходяться адреси операндів. Кожна адреса займає mi розрядів, де і - номер адреси (і=1,2,. n), n - кількість адресних полів. Кожною адресою можна адресувати память ємністю 2 слів.

Розмір команди k + m повинен бути узгодженим з розміром даних, тобто бути з ним однаковим або кратним цілому числу, що спрощує організацію роботи з памяттю. Як правило, розмір команди рівний 8, 16, 32 біти.

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

(000011111111) 2 = (0377) 8 = (0FF) 16 ;

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

ADD - додати (add),

SUB - відняти (subtract),

MPY - перемножити (multiply),

DIV - поділити (divide).

Операнди також представляються символічно. Наприклад команда ADD R Y означає додавання вмісту комірки памяті Y до вмісту регістра R. Зауважимо, що операція виконується над вмістом, а не над адресою комірки памяті та регістра.

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

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

Способи адресації

Варіанти інтерпретації бітів (розрядів) поля адреси з метою знаходження операнда називаються способами адресації. Коли команда вказує на операнд, він може знаходитись в самій команді, в основній або зовнішній памяті чи в регістровій памяті процесора. За роки існування компютерів була створена своєрідна технологія адресації, яка передбачає реалізацію різних способів адресації, чому послужило ряд причин: забезпечення ефективного використання розрядної сітки команди; забезпечення ефективної апаратної підтримки роботи з масивами даних; забезпечення задання параметрів операндів; можливість генерації великих адрес на основі малих. Існує велика кількість способів адресації. Розглянемо п’ять основних способів адресації операндів в командах.

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

Безпосередня - в поле адреси команди поміщається не адреса, а сам операнд.

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

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

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

Вихідні дані на проектування

Варіант №9

Арифметичні

Логічні

Керування

Прапорці

Адресація

1

2

3

4

5

6

7

8

1

2

3

9

3

5

9

1

2

17

2

10

ZF

2

3

5

1

1. Реалізація додаткових команд. Необхідно реалізувати 8 додаткових команд. Серед них 3 арифметичні, 3 логічні та 2 команди керування згідно варіанту. В таблиці представлено повний перелік множини інструкцій.

Код інструкції

Двійкове значення

Зміст

Тип

1

ADD

00000

Додає вміст регістру regA до вмісту regB, та зберігає в destReg

R

2

DIV

01000

Беззнакове ділення destReg=regA/regB

R

3

IMUL

01001

Знакове множення destReg=regA*regB

R

4

XIDIV

01010

Знакове ділення і обмін операндів місцями destReg=regA/regB

R

5

AND

01011

Побітове логічне І: destReg=regA regB

R

6

NAND

00001

Виконує логічне побітове І-НЕ вмісту regA з вмістом regB, та

зберігає в destReg

R

7

XOR

01100

Додавання по модулю 2: destReg=regA # regB

R

8

CMPGE

01101

Порівняти regA regB destReg= regA = regB

R

Інструкції R-типу:

біти 24-22: код операції

біти 21-19: reg A

біти 18-16: reg B

біти 15-3: не використовуються (=0)

біти 2-0: destReg

Код

інструкції

Двійкове

значення

Зміст

Тип

9

LW

00010

Завантажує regB з пам’яті. Адреса пам’яті формується

додаванням зміщення до вмісту regA.

I

10

SW

00011

Зберігає вміст регістру regB в пам’ять. Адреса пам’яті

формується додаванням зміщення до вмісту regA.

I

11

BEQ

00100

Якщо вміст регістрів regA та regB однаковий, виконується

перехід на адресу програмний лічильник (ПЛ) + 1+зміщення, в ПЛ зберігається адреса поточної тобто beq інструкції.

I

12

JMAE

01110

Беззнакове більше/рівно if (regA= regB) PC=PC+1+offSet

I

13

JMNAE

01111

Беззнакове не більше/рівно if (regA! = regB) PC=PC+1+offSet

I

14

JNE

10010

Перейти, якщо більше чи рівно, if (ZF!

=0) PC=offset

I

I-тип інструкцій:

біти 24-22: код операції

біти 21-19: reg A

біти 18-16: reg B

біти 15-0: зміщення (16 біт, значення від - 32768 до 32767)

offset

Код інструкції

Двійкове значення

Зміст

Тип

15

JARL

00101

Спочатку зберігає ПЛ+1 в regB, в ПЛ адреса поточної (jalr)

інструкції. Виконує перехід на адресу, яка зберігається в regA. Якщо в якості regA regB задано один і той самий регістр, то спочатку в цей регістр запишеться ПЛ+1, потім виконається перехід до ПЛ+1.

J

16

BSF

10000

Побітове сканування в прямому (від молодших до старших) напрямку regA в пошуках біта з 1, повертає номер позиції в destReg. Якщо 1 знайдено ZF=1, інакше ZF=0

J

17

BSR

10001

Побітове сканування в зворотньому напрямку (від старших до молодших) regA в пошуках біта з 1, повертає номер позиції в destReg. Якщо 1

знайдено ZF=1, інакше ZF=0

J

J-тип інструкцій:

біти 24-22: код операції

біти 21-19: reg A

біти 18-16: reg B

біти 15-0: не використовуються (=0)

unused

Додатковий тип адресації - безадресна (реалізація стеку). Ця адресація имагає двох додаткових інструкцій: PUSH, POP.

Код інструкції

Двійкове значення

Зміст

Тип

18

HALT

00110

Збільшує значення ПЛ на 1, потім припиняє виконання,

стимулятор має повідомляти, що виконано зупинку.

O

19

NOOP

00111

Нічого не виконується

O

20

PUSH

10011

Записати в стек з 1 регістру

O

21

POP

10100

Зчитати з стеку в 1 регістр

O

O-тип інструкцій:

біти 24-22: код операції

біти 21-0: не використовуються (=0)

unused

\

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

Структура СК після модифікації:

Рис 1. Функціональна схема СК після модифікації

В СК було додано 13 інструкцій, внаслідок чого поле КОП збільшилося на 2 біти до 5 біт (максимально 32 інструкції з яких використано 21). Решта частина коду операції не зазнала зміни. Був доданий стек глибиною 32 слова по 32 біти, покажчик вершини стеку, прапорець стану ZF.

Перевірка правильності роботи реалізованих команд у прикладах

1. div: 45/5=9.


Програма:

lw 0 1 num1

lw 0 2 num2

div 1 2 3

done halt

num1 .fill 45

num2 .fill 5

Машинний код:

8454148

8519685

34209795

25165824

45

5


Кінцевий стан:

@@@

state:

pc 4

ZF = 0

stack:

memory:

mem[ 0 ] 8454148

mem[ 1 ] 8519685

mem[ 2 ] 34209795

mem[ 3 ] 25165824

mem[ 4 ] 45

mem[ 5 ] 5

registers:

reg[ 0 ] 0

reg[ 1 ] 45

reg[ 2 ] 5

reg[ 3 ] 9

reg[ 4 ] 0

reg[ 5 ] 0

reg[ 6 ] 0

reg[ 7 ] 0

end state

2. imul: 3*(-4)=-12.


Програма:

lw 0 1 num1

lw 0 2 num2

imul 1 2 3

done halt

num1 .fill 3

num2 .fill -4

Машинний код:

8454148

8519685

34209795

25165824

3

-4


Кінцевий стан:

@@@

state:

pc 4

ZF = 0

stack:

memory:

mem[ 0 ] 8454148

mem[ 1 ] 8519685

mem[ 2 ] 38404099

mem[ 3 ] 25165824

mem[ 4 ] 3

mem[ 5 ] -4

registers:

reg[ 0 ] 0

reg[ 1 ] 3

reg[ 2 ] -4

reg[ 3 ] -12

reg[ 4 ] 0

reg[ 5 ] 0

reg[ 6 ] 0

reg[ 7 ] 0

end state

3. xidiv: 30/(-5)=-6, обміняти операнди місцями.


Програма:

lw 0 1 num1

lw 0 2 num2

xidiv 1 2 3

done halt

num1 .fill 30

num2 .fill -5

Машинний код:

8454148

8519685

42598403

25165824

30

-5


Кінцевий стан:

@@@

state:

pc 4

ZF = 0

stack:

memory:

mem[ 0 ] 8454148

mem[ 1 ] 8519685

mem[ 2 ] 42598403

mem[ 3 ] 25165824

mem[ 4 ] 30

mem[ 5 ] -5

registers:

reg[ 0 ] 0

reg[ 1 ] -5

reg[ 2 ] 30

reg[ 3 ] -6

reg[ 4 ] 0

reg[ 5 ] 0

reg[ 6 ] 0

reg[ 7 ] 0

end state

4.and: 53=1

0101

0011

0001


Програма:

lw 0 1 num1

lw 0 2 num2

andf 1 2 3

done halt

num1 .fill 5

num2 .fill 3

Машинний код:

8454148

8519685

46792707

25165824

5

3


Кінцевий стан:

@@@

@@@

state:

pc 4

ZF = 0

stack:

memory:

mem[ 0 ] 8454148

mem[ 1 ] 8519685

mem[ 2 ] 46792707

mem[ 3 ] 25165824

mem[ 4 ] 5

mem[ 5 ] 3

registers:

reg[ 0 ] 0

reg[ 1 ] 5

reg[ 2 ] 3

reg[ 3 ] 1

reg[ 4 ] 0

reg[ 5 ] 0

reg[ 6 ] 0

reg[ 7 ] 0

end state

xor: 5#3=6

0101

#

0011

0110


Програма:

lw 0 1 num1

lw 0 2 num2

xorf 1 2 3

done halt

num1 .fill 3

num2 .fill 5

Машинний код:

8454148

8519685

50987011

25165824

3

5


Кінцевий стан:

@@@

state:

pc 4

ZF = 0

stack:

memory:

mem[ 0 ] 8454148

mem[ 1 ] 8519685

mem[ 2 ] 50987011

mem[ 3 ] 25165824

mem[ 4 ] 3

mem[ 5 ] 5

registers:

reg[ 0 ] 0

reg[ 1 ] 3

reg[ 2 ] 5

reg[ 3 ] 6

reg[ 4 ] 0

reg[ 5 ] 0

reg[ 6 ] 0

reg[ 7 ] 0

end state

6. cmpge: 1= 5=3


Програма:

@@@

state:

pc 4

ZF = 0

stack:

memory:

mem[ 0 ] 8454148

mem[ 1 ] 8519685

mem[ 2 ] 55181315

mem[ 3 ] 25165824

mem[ 4 ] 5

mem[ 5 ] 3

registers:

reg[ 0 ] 0

reg[ 1 ] 5

reg[ 2 ] 3

reg[ 3 ] 1

reg[ 4 ] 0

reg[ 5 ] 0

reg[ 6 ] 0

reg[ 7 ] 0

end state

7. jma: if (7=4) reg[4]=7; else reg[5]=4, reg[4]=7.


Програма:

lw 0 1 num1

lw 0 2 num2

jmae 1 2 1

lw 0 5 num2

lw 0 4 num1

done halt

num1 .fill 7

num2 .fill 4

Машинний код:

8454150

8519687

59375617

8716295

8650758

25165824

7

3


Кінцевий стан:

@@@

state:

pc 6

ZF = 0

stack:

memory:

mem[ 0 ] 8454150

mem[ 1 ] 8519687

mem[ 2 ] 59375617

mem[ 3 ] 8716295

mem[ 4 ] 8650758

mem[ 5 ] 25165824

mem[ 6 ] 7

mem[ 7 ] 4

registers:

reg[ 0 ] 0

reg[ 1 ] 7

reg[ 2 ] 4

reg[ 3 ] 0

reg[ 4 ] 7

reg[ 5 ] 0

reg[ 6 ] 0

reg[ 7 ] 0

end state

8. jmnae: if (5!=6) reg[4]=6; else reg[4]=5, reg[5]=6.


Програма:

lw 0 1 num1

lw 0 2 num2

jmnae 1 2 1

lw 0 5 num2

lw 0 4 num1

done halt

num1 .fill 5

num2 .fill 6

Машинний код:

8454150

8519687

63569921

8716295

8650758

25165824

5

6


Кінцевий стан:

@@@

state

pc 6

ZF = 0

stack:

memory:

mem[ 0 ] 8454150

mem[ 1 ] 8519687

mem[ 2 ] 63569921

mem[ 3 ] 8716295

mem[ 4 ] 8650758

mem[ 5 ] 25165824

mem[ 6 ] 5

mem[ 7 ] 6

registers:

reg[ 0 ] 0

reg[ 1 ] 5

reg[ 2 ] 6

reg[ 3 ] 0

reg[ 4 ] 5

reg[ 5 ] 0

reg[ 6 ] 0

reg[ 7 ] 0

end state

9. bsr: if (16=1000) “1” – в позиції 4.


Програма:

lw 0 1 num1

bsr 1 2

done halt

num1 .fill 16

Машинний код:

8454150

8519687

25165824

16


Кінцевий стан:

@@@

state:

pc 3

ZF = 1

stack:

memory:

mem[ 0 ] 8454147

mem[ 1 ] 71958528

mem[ 2 ] 25165824

mem[ 3 ] 16

registers:

reg[ 0 ] 0

reg[ 1 ] -2147483648

reg[ 2 ] 4

reg[ 3 ] 0

reg[ 4 ] 0

reg[ 5 ] 0

reg[ 6 ] 0

reg[ 7 ] 0

end state

10. bsf: 8 (1000) – «1» в позиції 3.


Програма:

lw 0 1 num1

bsf 1 2

done halt

num1 .fill 8

Машинний код:

8454147

71958528

25165824

8


Кінцевий стан:

@@@

@@@

state:

pc 3

ZF = 1

stack:

memory:

mem[ 0 ] 8454147

mem[ 1 ] 67764224

mem[ 2 ] 25165824

mem[ 3 ] 8

registers:

reg[ 0 ] 0

reg[ 1 ] 1

reg[ 2 ] 3

reg[ 3 ] 0

reg[ 4 ] 0

reg[ 5 ] 0

reg[ 6 ] 0

reg[ 7 ] 0

end state

11. jne: if (16=1000) “1” – в позиції 4, ZF=1: Робота програми завершується, else reg[4]=8.


Програма:

lw 0 1 num1

bsr 1 0 3

jne 0 0 4

lw 0 4 num1

done halt

num1 .fill 8

Машинний код:

8454150

8519687

67764224

8454147

25165824

8


Кінцевий стан:

@@@

state:

@@@

state:

pc 5

ZF = 1

stack:

memory:

mem[ 0 ] 8454149

mem[ 1 ] 71827456

mem[ 2 ] 75497476

mem[ 3 ] 8650757

mem[ 4 ] 25165824

mem[ 5 ] 8

registers:

reg[ 0 ] 0

reg[ 1 ] -2147483648

reg[ 2 ] 0

reg[ 3 ] 0

reg[ 4 ] 0

reg[ 5 ] 0

reg[ 6 ] 0

reg[ 7 ] 0

end state

12. push, pop: push 2, push 3, pop, pop.


Програма:

lw 0 1 num1

push

lw 0 1 num2

push

pop

pop

done halt

num1 .fill 2

num2 .fill 3

Машинний код:

8454150

79691776

8454151

79691776

83886080

83886080

25165824

2

3


Проміжний стан, після двох push:

@@@

state:

pc 4

ZF = 0

stack:

stk[ 0 ] 2

stk[ 1 ] 3

memory:

mem[ 0 ] 8454151

mem[ 1 ] 79691776

mem[ 2 ] 8454152

mem[ 3 ] 79691776

mem[ 4 ] 83886080

mem[ 5 ] 83886080

mem[ 6 ] 25165824

mem[ 7 ] 2

mem[ 8 ] 3

registers:

reg[ 0 ] 0

reg[ 1 ] 3

reg[ 2 ] 0

reg[ 3 ] 0

reg[ 4 ] 0

reg[ 5 ] 0

reg[ 6 ] 0

reg[ 7 ] 0

end state

Кінцевий стан:

@@@

state:

pc 7

ZF = 0

stack:

memory:

mem[ 0 ] 8454151

mem[ 1 ] 79691776

mem[ 2 ] 8454152

mem[ 3 ] 79691776

mem[ 4 ] 83886080

mem[ 5 ] 83886080

mem[ 6 ] 25165824

mem[ 7 ] 2

mem[ 8 ] 3

registers:

reg[ 0 ] 0

reg[ 1 ] 2

reg[ 2 ] 0

reg[ 3 ] 0

reg[ 4 ] 0

reg[ 5 ] 0

reg[ 6 ] 0

reg[ 7 ] 0

end state

Висновок

При виконанні даного курсового проекту було реалізовано прототипний CISC - комп’ютер згідно із поставленим завданням. Створений комп’ютер пройшов тестування на коректність виконуваних операцій та на відловлювання помилок у вхідному асемблерному коді при синтаксичному та семантичному аналізі. Засвоєно принципи дії та архітектуру прототипних варіантів CISC - комп’ютера. Було внесено зміни в структуру існуючого симулятора CISC - комп’ютера, а саме, доповнена система команд заданими інструкціями, змінено формат усіх команд в частині КОП. До існуючих типів адресації CISC - комп’ютера було добавлено безадресний тип адресації, що в свою чергу призвело до створення стеку всередині структури комп’ютера. Було проведено аналіз роботи команд усіх типів та написано тести з поданням результату роботи симулятора у вигляді виведеного стану машини.

Література

1. Мельник А.О. Архітектура комп’ютера. Наукове видання. - Луцьк: Волинська обласна друкарня, 2008. - 470 с.

2. Жмакин А.П. Архитектура ЭВМ. - СПб.: БХВ-Петербург, 2006. - 320 с.

3. Таненбаум Э. Архитектура компьютера.5-е изд. (+CD). - СПб.: Питер, 2007. - 844 с.

4. Patterson D., and Hennessy J.computer Architecture. A quantitative Approach. Second Edition. - Morgan Kaufmann Publishers, Inc., San Francisco, California, 1996. - 760 p.

Додатки

Доаток I (код програми-асемблера):

/* Assembler for LC */

#include stdlib. h

#include stdio. h

#include string. h

#define MAXLINELENGTH 1000

#define MAXNUMLABELS 65536

#define MAXLABELLENGTH 7 /* includes the null character termination */

#define ADD 0

#define NAND 1

#define LW 2

#define SW 3

#define BEQ 4

#define JALR 5

#define HALT 6

#define NOOP 7

#define div 8

#define imul 9

#define xidiv 10

#define andf 11

#define xorf 12

#define cmpge 13

#define jmae 14

#define jmnae 15

#define bsf 16

#define bsr 17

#define jne 18

#define push 19

#define pop 20

int readandfParse (FILE *, char *, char *, char *, char *, char *);

int translateSymbol (char labelArray [MAXNUMLABELS] [MAXLABELLENGTH], int labelAddress [MAXNUMLABELS], int, char *);

int isNumber (char *);

void testRegArg (char *);

void testAddrArg (char *);

int main (int argc, char *argv [])

{

char *inFileString, *outFileString;

FILE *inFilePtr, *outFilePtr;

int address;

char label [MAXLINELENGTH], opcode [MAXLINELENGTH], arg0 [MAXLINELENGTH],

arg1 [MAXLINELENGTH], arg2 [MAXLINELENGTH], argTmp [MAXLINELENGTH];

int i;

int numLabels=0;

int num;

int addressField;

char labelArray [MAXNUMLABELS] [MAXLABELLENGTH];

int labelAddress [MAXNUMLABELS];

if (argc! = 3) {

printf (error: usage: %s assembly-code-file machine-code-file\n,

argv [0]);

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; readandfParse (inFilePtr, label, opcode, arg0, arg1, arg2);

address++) {

/*

printf (%d: label=%s, opcode=%s, arg0=%s, arg1=%s, arg2=%s\n,

address, label, opcode, arg0, arg1, arg2);

*/

/* 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, div)

strcmp (opcode, imul) strcmp (opcode, xidiv)

strcmp (opcode, andf) strcmp (opcode, xorf)

strcmp (opcode, cmpge) strcmp (opcode, jmae)

strcmp (opcode, jmnae) strcmp (opcode, bsr)

strcmp (opcode, jne) strcmp (opcode, bsf)

strcmp (opcode, push) strcmp (opcode, pop))

{

printf (error: unrecognized opcode %s at address %d\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, imul) ||

! strcmp (opcode, xidiv) ||! strcmp (opcode, andf) ||

! strcmp (opcode, xorf) ||! strcmp (opcode, cmpge) ||

! strcmp (opcode, bsf) ||! strcmp (opcode, bsr))

{

testRegArg (arg0);

testRegArg (arg1);

}

if (! strcmp (opcode, nand) ||! strcmp (opcode, add) ||

! strcmp (opcode, div) ||! strcmp (opcode, imul) ||

! strcmp (opcode, xidiv) ||! strcmp (opcode, andf) ||

! strcmp (opcode, xorf) ||! strcmp (opcode, cmpge))

{

testRegArg (arg2);

}

/* check addressField */

if (! strcmp (opcode, lw) ||! strcmp (opcode, sw) ||

! strcmp (opcode, beq) ||! strcmp (opcode, jmae) ||

! strcmp (opcode, jmnae) ||! strcmp (opcode, jne))

{

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, bsf) strcmp (opcode, bsr)

strcmp (opcode, pop) strcmp (opcode, push)

strcmp (opcode, je) arg2 [0] ==\0) ||

(! strcmp (opcode, jalr) arg1 [0] ==\0) ||

(! strcmp (opcode,. fill) arg0 [0] ==\0))

{

printf (error at address %d: 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 doesnt start with letter\n);

exit (2);

}

/* make sure label consists of only letters andf numbers */

sscanf (label, % [a-zA-Z0-9], argTmp);

if (strcmp (argTmp, label)) {

printf (label has character other than letters andf numbers\n);

exit (2);

}

/* look for duplicate label */

for (i=0; inumLabels; i++) {

if (! strcmp (label, labelArray [i])) {

printf (error: duplicate label %s at address %d\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);

}

strcpy (labelArray [numLabels], label);

labelAddress [numLabels++] = address;

}

}

for (i=0; inumLabels; 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; readandfParse (inFilePtr, label, opcode, arg0, arg1, arg2);

address++) {

if (! strcmp (opcode, add)) {

num = (ADD 22) | (atoi (arg0) 19) | (atoi (arg1) 16) | atoi (arg2);

} else if (! strcmp (opcode, nand)) {

num = (NAND 22) | (atoi (arg0) 19) | (atoi (arg1) 16) | atoi (arg2);

} else if (! strcmp (opcode, div)) {

num = (div 22) | (atoi (arg0) 19) | (atoi (arg1) 16) | atoi (arg2);

} else if (! strcmp (opcode, imul)) {

num = (imul 22) | (atoi (arg0) 19) | (atoi (arg1) 16) | atoi (arg2);

} else if (! strcmp (opcode, xidiv)) {

num = (xidiv 22) | (atoi (arg0) 19) | (atoi (arg1) 16) | atoi (arg2);

} else if (! strcmp (opcode, andf)) {

num = (andf 22) | (atoi (arg0) 19) | (atoi (arg1) 16) | atoi (arg2);

} else if (! strcmp (opcode, xorf)) {

num = (xorf 22) | (atoi (arg0) 19) | (atoi (arg1) 16) | atoi (arg2);

} else if (! strcmp (opcode, cmpge)) {

num = (cmpge 22) | (atoi (arg0) 19) | (atoi (arg1) 16) | atoi (arg2);

} else if (! strcmp (opcode, jalr)) {

num = (JALR 22) | (atoi (arg0) 19) | (atoi (arg1) 16);

} else if (! strcmp (opcode, bsf)) {

num = (bsf 22) | (atoi (arg0) 19) | (atoi (arg1) 16);

} else if (! strcmp (opcode, push)) {

num = (push 22);

} else if (! strcmp (opcode, pop)) {

num = (pop 22);

} else if (! strcmp (opcode, halt)) {

num = (HALT 22);

} else if (! strcmp (opcode, noop)) {

num = (NOOP 22);

} else if (! strcmp (opcode, bsr)) {

num = (bsr 22) | (atoi (arg0) 19) | (atoi (arg1) 16);

} else if (! strcmp (opcode, lw) ||! strcmp (opcode, sw) ||

! strcmp (opcode, beq) ||! strcmp (opcode, jmae) ||

! strcmp (opcode, jmnae) ||! strcmp (opcode, jne)) {

/* 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) ||! strcmp (opcode, jmae) ||! strcmp (opcode, jmnae)) {

addressField = addressField-address-1;

}

} else {

addressField = atoi (arg2);

}

if (addressField - 32768 || addressField 32767) {

printf (error: offset %d out of range\n, addressField);

exit (1);

}

/* truncate the offset field, in case its negative */

addressField = addressField 0xFFFF;

if (! strcmp (opcode, beq)) {

num = (BEQ 22) | (atoi (arg0) 19) | (atoi (arg1) 16)

| addressField;

} else if (! strcmp (opcode, jmae)) {

num = (jmae 22) | (atoi (arg0) 19) | (atoi (arg1) 16)

| (addressField);

} else if (! strcmp (opcode, jmnae)) {

num = (jmnae 22) | (atoi (arg0) 19) | (atoi (arg1) 16)

| (addressField);

} else if (! strcmp (opcode, jne)) {

num = (jne 22) | (addressField);

} else {

/* lw or sw */

if (! strcmp (opcode, lw)) {

num = (LW 22) | (atoi (arg0) 19) |

(atoi (arg1) 16) | addressField;

} else {

num = (SW 22) | (atoi (arg0) 19) |

(atoi (arg1) 16) | addressField;

}

}

} else if (! strcmp (opcode,. fill)) {

if (! isNumber (arg0)) {

num = translateSymbol (labelArray, labelAddress, numLabels,

arg0);

} else {

num = atoi (arg0);

}

}

/* printf ( (address %d): %d (hex 0x%x) \n, address, num, num); */

fprintf (outFilePtr, %d\n, num);

}

exit (0);

}

/*

* Read andf parse a line of the assembly-language file. Fields are returned

* in label, opcode, arg0, arg1, arg2 (these strings must have memory already

* allocated to them).

*

* Return values:

* 0 if reached end of file

* 1 if all went well

*

* exit (1) if line is too long.

*/

int readandfParse (FILE *inFilePtr, char *label, char *opcode, char *arg0,char *arg1, char *arg2)

{

char line [MAXLINELENGTH];

char *ptr = line;

/* delete prior values */

label [0] = opcode [0] = arg0 [0] = arg1 [0] = arg2 [0] = \0;

/* read the line from the assembly-language file */

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);

}

/* is there a label? */

ptr = line;

if (sscanf (ptr, % [^\t\n], label)) {

/* successfully read label; advance pointer over the label */

ptr += strlen (label);

}

/*

* Parse the rest of the line. Would be nice to have real regular

* expressions, but scanf will suffice.

*/

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);

}

int translateSymbol (char labelArray [MAXNUMLABELS] [MAXLABELLENGTH],

int labelAddress [MAXNUMLABELS], int numLabels, char *symbol)

{

int i;

/* search through address label table */

for (i=0; inumLabels strcmp (symbol, labelArray [i]); i++) {

}

if (i=numLabels) {

printf (error: missing label %s\n, symbol);

exit (1);

}

return (labelAddress [i]);

}

int isNumber (char *string)

{

/* return 1 if string is a number */

int i;

return ( (sscanf (string, %d, i)) == 1);

}

/* Test register argument; make sure its in range andf has no bad characters. */

void testRegArg (char *arg)

{

int num;

char c;

if (atoi (arg) 0 || atoi (arg) 7) {

printf (error: register out of range\n);

exit (2);

}

if (sscanf (arg, %d%c, num, c)! = 1) {

printf (bad character in register argument\n);

exit (2);

}

}

/* Test addressField argument. */

void testAddrArg (char *arg)

{

int num;

char c;

/* test numeric addressField */

if (isNumber (arg)) {

if (sscanf (arg, %d%c, num, c)! = 1) {

printf (bad character in addressField\n);

exit (2);

}

}

}
Додаток II (код симулятора)

/*Instruction-level simulator for the LC */

#include stdio. h

#include stdlib. h

#include string

#include math. h

#include vector

using namespace std;

#define NUMMEMORY 65536 /* maximum number of words in memory */

#define NUMREGS 8 /* number of machine registers */

#define MAXLINELENGTH 1000

#define STACKDEPTH 32

#define ADD 0

#define NAND 1

#define LW 2

#define SW 3

#define BEQ 4

#define JALR 5

#define HALT 6

#define NOOP 7

#define div 8

#define imul 9

#define xidiv 10

#define andf 11

#define xorf 12

#define cmpge 13

#define jmae 14

#define jmnae 15

#define bsf 16

#define bsr 17

#define jne 18

#define push 19

#define pop 20

typedef struct stateStruct {

int pc;

int mem [NUMMEMORY];

int reg [NUMREGS];

int numMemory;

vector int sStack;

int ZF;

} stateType;

void printState (stateType *);

void run (stateType);

int convertNum (int);

int main (int argc, char *argv [])

{

int i;

char line [MAXLINELENGTH];

stateType state;

FILE *filePtr;

if (argc! = 2) {

printf (error: usage: %s machine-code file\n, argv [0]);

exit (1);

}

/* initialize memories and registers */

for (i=0; iNUMMEMORY; i++) {

state. mem [i] = 0;

}

for (i=0; iNUMREGS; i++) {

state. reg [i] = 0;

}

state. ZF=0;

state. pc=0;

/* read machine-code file into instruction/data memory (starting at

address 0) */

filePtr = fopen (argv [1], r);

if (filePtr == NULL) {

printf (error: cant 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, %d, state. mem+state. numMemory)! = 1) {

printf (error in reading address %d\n, state. numMemory);

exit (1);

}

printf (memory [%d] =%d\n, state. numMemory, state. mem [state. numMemory]);

}

printf (\n);

/* run never returns */

run (state);

return (0);

}

void run (stateType state)

{

int i;

int arg0, arg1, arg2, addressField;

int instructions=0;

int opcode;

int maxMem=-1; /* highest memory address touched during run */

for (; 1; instructions++) { /* infinite loop, exits when it executes halt */

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] 22;

arg0 = (state. mem [state. pc] 19) 0x7;

arg1 = (state. mem [state. pc] 16) 0x7;

arg2 = state. mem [state. pc] 0x7; /* only for add, nand */

addressField = convertNum (state. mem [state. pc] 0xFFFF); /* for beq,

lw, sw */

state. pc++;

if (opcode == ADD) {

state. reg [arg2] = state. reg [arg0] + state. reg [arg1];

} else if (opcode == NAND) {

state. reg [arg2] = ~ (state. reg [arg0] state. reg [arg1]);

} else if (opcode == LW) {

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];

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 == JALR) {

state. reg [arg1] = state. pc;

if (arg0! = 0)

state. pc = state. reg [arg0];

else

state. pc = 0;

} else if (opcode == NOOP) {

} else if (opcode == HALT) {

printf (machine halted\n);

printf (total of %d instructions executed\n, instructions+1);

printf (final state of machine: \n);

printState (state);

exit (0);

} else if (opcode == xidiv) {

state. reg [arg2] = state. reg [arg0] / state. reg [arg1];

state. reg [arg0] ^=state. reg [arg1] ^=state. reg [arg0] ^=state. reg [arg1];

} else if (opcode == div) {

state. reg [arg2] = (unsigned) state. reg [arg0] / (unsigned) state. reg [arg1];

} else if (opcode == imul) {

state. reg [arg2] = state. reg [arg0] * state. reg [arg1];

} else if (opcode == andf) {

state. reg [arg2] = state. reg [arg0] state. reg [arg1];

} else if (opcode == xorf) {

state. reg [arg2] = state. reg [arg0] xor state. reg [arg1];

} else if (opcode == cmpge) {

state. reg [arg2] = state. reg [arg0] = state. reg [arg1];

} else if (opcode == jmae) {

if ( (unsigned) state. reg [arg0] = (unsigned) state. reg [arg1])

state. pc += addressField;

} else if (opcode == jmnae) {

if ( (unsigned) state. reg [arg0] (unsigned) state. reg [arg1])

state. pc += addressField;

} else if (opcode == bsf) {

state. ZF=0;

for (i=0; i31; i++) {

if (state. reg [arg0] %2==1) {

state. ZF=1;

state. reg [arg1] =i;

break;

}

else {

state. reg [arg0] =state. reg [arg0] 1;

}

}

} else if (opcode == bsr) {

state. ZF=0;

for (i=31; i0; i--) {

if (state. reg [arg0] /0x80000000==1) {

state. ZF=1;

state. reg [arg1] =i;

break;

}

else {

state. reg [arg0] =state. reg [arg0] 1;

}

}

} else if (opcode == jne) {

if (state. ZF! =0) state. pc = addressField;

} else if (opcode == push) {

if (state. sStack. size () STACKDEPTH - 1)

{

printf (\nerror: stack overflow! 0x%x\n, opcode);

printf (total of %d instructions executed\n, instructions+1);

printf (final state of machine: \n);

printState (state);

exit (1);

}

else state. sStack. push_back (state. reg [1]);

} else if (opcode == pop) {

if (state. sStack. empty ())

{

printf (\nerror: stack underflow! 0x%x\n, opcode);

printf (total of %d instructions executed\n, instructions+1);

printf (final state of machine: \n);

printState (state);

exit (1);

}

else

{

state. reg [1] = state. sStack. back ();

state. sStack. pop_back ();

}

} else {

printf (error: illegal opcode 0x%x\n, opcode);

exit (1);

}

state. reg [0] = 0;

}

}

void

printState (stateType *statePtr)

{

int i;

printf (\n@@@\nstate: \n);

printf (\tpc %d\n, statePtr-pc);

printf (\t\tZF = %d\n,statePtr-ZF);

vector int:: iterator stackiter;

printf (\tstack: \n);

for (stackiter = statePtr-sStack. begin (), i = 0; stackiter statePtr-sStack. end (); stackiter++, i++) {

printf (\t\tstek [%d] %d\n, i, *stackiter);

}

printf (\tmemory: \n);

for (i=0; istatePtr-numMemory; i++) {

printf (\t\tmem [%d] %d\n, i, statePtr-mem [i]);

}

printf (\tregisters: \n);

for (i=0; iNUMREGS; i++) {

printf (\t\treg [%d] %d\n, i, statePtr-reg [i]);

}

printf (end state\n);

}

int convertNum (int num)

{

/* convert a 16-bit number into a 32-bit Sun integer */

if (num (115)) {

num - = (116);

}

return (num);

}

Скачать архив с текстом документа