Программная модель вычислительной системы.
Эмулятор
Эмулятор — это программа, позволяющая запускать и отлаживать программы, созданные для оборудования, отличного от стандартного ПК. Отладчик содержит в своём составе процедуры моделирования операций, выполняемых в ходе функционирования. Команды имеют длину один или два байта. Биты 0−3 первого байта содержат код команды в соответствии с таблицей команд. Биты 4−7 и второй байт используются для здания… Читать ещё >
Программная модель вычислительной системы. Эмулятор (реферат, курсовая, диплом, контрольная)
Министерство Образования и Науки Украины Севастопольский Национальный Технический Университет Кафедра кибернетики и вычислительной техники Пояснительная записка к курсовому проекту
«Программная модель вычислительной системы. Эмулятор»
по дисциплине «Программирование»
Выполнил: студент группы М-22д Маслов В.О.
Проверил: Смагина А.О.
Севастополь 2010
Содержание Введение
1. Постановка задачи
2. Описание входных и выходных данных
3. Разработка алгоритма
4. Разработка эмулятора
5. Отладка и тестирование Заключение
Библиографический список Приложение
Введение
В настоящее время в связи с бурным развитием компьютерных технологий, все большее предпочтение среди программистов получают языки высокого уровня; однако, во многих случаях, программы, написанные на таких языках, теряют в быстродействии по сравнению с машинно-ориентированными языками (ассемблерами), а размер программы, написанной на языке высокого уровня почти всегда превышает (причем значительно) размер программы, написанной на ассемблере. Таким образом, для написания быстродействующих компактных программ (например, драйверов, компьютерных вирусов, резидентных программ и т. п.) следует использовать языки низкого уровня. Несмотря на развитие языков программирования высокого уровня, ассемблер нельзя назвать мертвым, изжившим себя языком, так как каким бы хорошим ни был оптимизирующий компилятор, он не заменит хорошего программиста на ассемблере; более того, ассемблер часто используется именно там, где необходимо обеспечить максимальное быстродействие или работу на «машинном» уровне.
Эмулятор — это программа, позволяющая запускать и отлаживать программы, созданные для оборудования, отличного от стандартного ПК. Отладчик содержит в своём составе процедуры моделирования операций, выполняемых в ходе функционирования.
1. Постановка задачи Целью разрабатываемого курсового проекта, является изучение внутреннего устройства ЭВМ с точки зрения программиста. В процессе выполнения проекта необходимо создать кросс-эмулятор — программную модель ЭВМ с заданной архитектурой.
Задана структура микропроцессорной системы (№ 3):
Объем оперативной памяти — 4096 байт.
Команды имеют длину один или два байта. Биты 0−3 первого байта содержат код команды в соответствии с таблицей команд. Биты 4−7 и второй байт используются для здания адреса памяти, где находится операнд или константа.
Арифметические и логические операции выполняются над операндом, находящемся в памяти и верхним элементом стека. Результат выполнения операции в память.
Список команд для структуры № 3 :
В соответствии с заданием, длина объектного кода команды составляет один или два байта, формат команды схематически изображен на следующем рисунке:
Код операции | Адрес | Адрес | |||||||||||||||
Байт № 1 | Байт № 2 | ||||||||||||||||
Рисунок 1 — изображение формата команды
2. Описание входных и выходных данных
Входные данные
Входными данными для эмулятора является объектный файл, сгенерированный ассемблером. Объектный файл содержит программу и указания эмулятору, как необходимо ее загружать. Формат объектного файла «признак-байт».
Программа на ассемблере помимо команд процессора содержит также две специальные директивы: ORG — указание на адрес в памяти, куда необходимо загружать последующие команды, и DATA — задание константы. Чтобы эмулятор мог отличить их друг от друга, каждая команда имеет свой байт-признак, указывающий ее тип:
01 — адрес; ADDRESS
02 — команда; COMMAND
03 — данные; DATA
Например: 01 10 02 E5 02 23 01 20 03 D2 означает, что начиная с адреса 10 идут 2 команды E5 23, а затем с адреса 20 идут данные D2.
Выходные данные
Выходными данными является вывод в окно терминала текущего состояния элементов стека (R0, R1, R2, R3, R4, R5, R6, R7), PC (счетчик адреса команд), rgCOM (регистр команд), значение флагов (ноль, отрицательный, переполнение) и текущее состояния ячеек оперативной памяти.
3. Разработка алгоритма
1. Открываем поток чтения из объектного файла, сгенерированного транслятором.
2. В цикле читаем из файла по два байта. В зависимости от первого байта, который является либо признаком адреса, либо признаком данных, второй байт используем по назначению: если первый байт является признаком адреса, то начинаем с запоминания этого адреса, а если первый байт является признаком данных, то помещаем в текущую ячейку памяти.
3. Когда файл прочтен, закрыть поток чтения из файла.
4. Выбираем из памяти команду, адрес которой хранится в счетчике адреса команд.
5. Анализируем код команды и определяем количество байтов в команде. Читаем из памяти операнды — исходные данные.
6. Выполняем операцию и при необходимости формируем признаки результата, фиксируя их в регистре флагов.
7. Изменяем значение в счетчике адреса команд, готовясь к выполнению следующей команды.
8. Выводим на экран результат работы программы.
4. Разработка эмулятора микропроцессорный память программа эмулятор Создаем пять классов: Main, Processor, ALU, Memory и Stack.
Рассмотрим подробно каждый класс.
· Класс Processor включает в себя:
int PC — СЧАК: указывает на адрес следующей команды.
int rgCOM — регистр команд: хранит код текущей команды.
Флаги результата int zero (ноль), int overflow (переполнение), int negative
(отрицательный).
Stack — структура данных с методом доступа к элементам LIFO (последним пришелпервым вышел), стек состоит из восьми элементов (R0, R1, R2, R3, R4, R5, R6, R7).
Error — переменная которая сообщает об ошибке (если такая имеется).
Эта переменная может принимать такие значения:
· 0 — ошибок нет
· 1 — переполнение стека
· 2 — неизвестная команда
· 3 — неизвестный байт-признак
· 4 — данные в начале файла
· 5 — после данных команда или адрес
· 6 — невозможно загрузить файл Методы класса Processor:
public Processor (Memory ram) — связывает процессор с памятью.
private void clear () — сбрасывает все значения элементов в процессоре и памяти.
public void execute () — метод выполняет всю программу: пока не достигнут конец программы.
public void executeOneCom () — выполняет одну команду.
private boolean twoBytes (int c) — определяет сколько байт в команде.
public boolean loadObjectFile (DataInput f) — считывает указанный объектный файл, загружает программу в память и устанавливает адрес последней команды (это необходимо сделать, т. к. иначе нельзя определить во время выполнения программы когда достигнут ее конец). В случае успешной загрузки он возвращает true и в служебную информацию вписывает сообщение об этом и адрес последней команды. Иначе возвращает false и устанавливает сообщение с указанным номером ошибки.
public String toString () — метод выводящий в терминал состояние стека, регистра команд, СЧАКа и значение флагов — после выполнения каждой команды.
Алгоритм загрузки программы в память:
flag — предыдущий признак, i — индекс следующей ячейки памяти, m — признак байта, b — байт
1. Прочитать 2 байта программы если они есть, иначе конец. m = признак байта, b = байт.
2. Если m = адрес, то:
a. если это начало программы (flag = 0), то установить счетчик команд в b
b. если flag = данные, вернуть сообщение об ошибке, конец
c. flag < адрес, i < b
d. переход к п. 1
3. Если m = команда, то:
4. если flag — данные, вернуть сообщение об ошибке, конец
a. flag < команда, конец программы < i+1
b. переход к п.6
5. Если m = данные, то:
a. если начало программы, то вернуть ошибку
b. flag < данные
c. переход к п.6
6. m неизвестно, вернуть сообщение об ошибке
7. записать в ячейку i байт b, i < i+1, переход к п.1
Загрузка следующей команды происходит следующим образом: в регистр команды загружается команда из памяти по адресу, указанному в счетчике адреса команды, который затем увеличивается на единицу. В зависимости от кода команды принимается решение о том, следует ли загружать второй байт из памяти и помещать его в регистр операнда АЛУ.
· Класс ALU включает в себя:
АЛУ реализовано как отдельный класс. В АЛУ содержатся два метода:
public int execute (int com) — определяет что за команда (по коду операции), и выполняет её.
private void setFlags (int r) — вызывает метод проверки флагов результата:
переполнение: если r < 0 или r > 255 то 1(переполнение есть) иначе 0 (нет):
отрицательный: если r < 128 то 1(отрицательный результат) иначе 0 (нет);
ноль: если r == 128 то 1(результат ноль) иначе 0 (нет).
Рассмотрим подробнее представление чисел в эмуляторе. По условию, ячейка памяти и регистры могут хранить байт информации — 8 бит. То есть можно представить числа от -128 до 127 (т. к. в списке команд есть операция вычитания). Старший бит — знаковый. Для хранения чисел используется тип int.
Реализация команд микропроцессора:
t — результат, reg — регистр АЛУ, proc. overflow — переполнение
Мнемокод | Код | Описание | Реализация | ||
ADD | Адрес | память[адрес]=стек[0]+ память[адрес] | proc.stack.peek ()+ram.read (reg); setFlags (t); ram.write (t & 0xFF, reg); | ||
ADC | Адрес | память[адрес]=стек[0]+ память[адрес]+ флаг переполнения | proc.stack.peek ()+ram.read (reg)+proc.overflow; setFlags (t); ram. write (t & 0xFF, reg); | ||
SUB | Адрес | память[адрес]=стек[0]-память[адрес] | proc.stack.peek ()-ram.read (reg); setFlags (t); ram. write (t & 0xFF, reg); | ||
SUB | Адрес | память[адрес]=стек[0]- память[адрес]- флаг переполнения | proc.stack.peek ()-ram.read (reg)-proc.overflow; setFlags (t); ram. write (t & 0xFF, reg); | ||
AND | Адрес | память[адрес]=стек[0]& память[адрес] | proc.stack.peek () & ram. read (reg); setFlags (t); ram. write (t & 0xFF, reg); | ||
OR | Адрес | память[адрес]=стек[0] | память[адрес] | proc.stack.peek () | ram. read (reg); setFlags (t); ram.write (t & 0xFF, reg); | ||
NOT | инверсия бит стек[0] | proc.stack.pop (); t = (~t) & 0xFF; setFlags (t); proc.stack.push (t); | |||
PUSH | Адрес | сдвиг стека; стек[0]=память[адрес] | if (!proc.stack.push (ram.read (reg))) { return 1; | ||
POP | Адрес | сдвиг стека; память[адрес]= стек[0] | ram.write (proc.stack.pop (), reg); | ||
INC | регистр | стек[0]=стек[0]+1 | proc.stack.pop (); t++; setFlags (t); proc.stack.push (t); | ||
DEC | регистр | стек[0]=стек[0]-1 | proc.stack.pop (); t—; setFlags (t); proc.stack.push (t); | ||
JMP | Адрес | безусловный переход | proc.PC = reg; | ||
JNZ | Адрес | переход, если не 0 | if (proc.zero == 0) proc. PC = reg; | ||
САLL | Адрес | переход к подпрограмме; адрес возврата сохраняется в регистре; | proc.PC-2; proc.PC = reg; if (!(proc.stack.push (t & 0xFF) && proc.stack.push (t &0xF00))) { return 1; | ||
RET | возврат из подпрограммы; адрес возврата находится в регистре; | int t1 = proc.stack.pop (); int t2 = proc.stack.pop (); t = t1 & t2; proc. PC = t; | |||
Таблица 1. Команды
· Класс Stack включает в себя методы для работы со стеком, а именно:
public Stack (int n) — по умолчанию все ячейки стека заполняются значением равным -1(не определено).
public int peek () — метод позволяющий узнать значение верхнего (нулевого) элемента стека.
public boolean push (int x) — позволяет добавить элемент в стек: для этого необходимо все элементы стека передвинуть — i-ый элемент передвигается в право на i+1, а на место нулевого добавляется нужный элемент.
public int pop () — позволяет вытащить (удалить) нулевой элемент стека, при этом все оставшиеся элементы сдвигаются на одну ячейку в лево.
· Класс Memory включает в себя:
Память представлена в виде массива целых: индекс — это адрес ячейки (addr), элемент — ее содержимое (data).
Метод public Memory () — метод создающий массив целых 4096 элементов.
public int size () — возвращает размер памяти.
public int read (int addr) — чтение адреса памяти.
public void write (int data, int addr) — запись данных в адрес памяти.
public void clear () — обнуляет значение ячеек памяти.
public String toString () — представление памяти как строк.
Память представлена в виде массива целых: индекс — это адрес ячейки (addr), элемент — ее содержимое (data).
· Класс Main содержит метод public static void main (String[] args) загружающий объектный файл ассемблера — p. loadObjectFile (new RandomAccessFile («f.bin», «r»)), выводит состояние ячеек памяти после выполнения команд System.out.println (ram);
Для записи программы в бинарный файл используется метод public static void writeprog () .
5. Отладка и тестирование Для тестирования программы написан пример программы, в которой используются команды, работающие со стеком, памятью, а также специальные команды ORG и DATA.
Тестовый пример:
ORG 2
PUSH 100
ADD 101
PUSH 100
POP 128
ADD 101
ORG 2
DATA 170
DATA 160
Содержимое объектного файла программы из листинга тестового примера:
01 02 02 70 02 64 02 00 02 65 02 70 02 65 02 80 02 80 02 00 02 64 01 64 03 AA 03 A0
Выполним программу вручную:
Начиная с адреса 2 в ячейки памяти будут заносится такие адреса: 70 64 0 65
Приложение А:
Листинг класса Processor:
import java.io.*;
import java.util.Arrays;
public class Processor {
ALU alu;
Memory ram;
Stack stack;
public int PC; // program counter указывает на адрес след. команды
public int rgCOM; // command register хранит код текущей команды
int end; // конец программы
int zero;
int overflow;
int negative;
/*
* 0 — ошибок нет
* 1 — переполнение стека
* 2 — неизвестная команда
* 3 — неизвестный байт-признак
* 4 — данные в начале файла
* 5 — после данных команда или адрес
* 6 — невозможно загрузить файл
*/
int error;
public Processor (Memory ram) {
this.ram = ram;
clear ();
}
/*
* сброс процессора и памяти
*/
private void clear () {
stack = new Stack (8);
PC = 0;
rgCOM = 0;
zero = 0;
overflow = 0;
negative = 0;
error = 0;
ram.clear ();
alu = new ALU (ram, this);
}
public void execute () {
while (PC ≠ end) executeOneCom ();
}
public void executeOneCom () {
if (error == 0) {
int first = ram. read (PC);
rgCOM = first >> 4;
PC++;
if (twoBytes (rgCOM))
// 0xF00 = 1111 0000 0000 (2 система)
alu.reg = (((first << 8) & 0xF00)
error = alu. execute (rgCOM);
if (error ≠ 0) {
System.out.println («Возникла ошибка: код ошибки «+ error);
} else {
System.out.println (this);
}
}
}
private boolean twoBytes (int c) {
if (c == 6 || c == 15 || c == 9 || c == 10) {
return false;
} else {
return true;
}
}
public boolean loadObjectFile (DataInput f) {
clear ();
final int ADDRESS = 1;
final int COMMAND = 2;
final int DATA = 3;
int i = 0; // индекс следующей ячейки памяти
int flag = 0;
int m; // признак байта
int b; // байт
while (true)
try {
m = f. readInt ();
b = f. readInt ();
if (m == ADDRESS) {
if (flag == 0) PC = b;
if (flag == DATA) {
error = 5;
return false;
}
flag = ADDRESS;
i = b;
} else {
if (m == COMMAND) {
if (flag == DATA) {
error = 5;
return false;
}
flag = COMMAND;
end = i+1;
} else if (m == DATA) {
if (flag == 0) {
error = 4;
return false;
}
flag = DATA;
} else {
error = 3;
return false;
}
ram.write (b, i);
i++;
}
} catch (EOFException e) {
return true;
} catch (IOException e) {
error = 6;
return false;
}
}
public String toString () {
StringBuilder s = new StringBuilder ();
s.append (String.format («stack:%sn», Arrays. toString (this.stack.s)));
s.append (String.format («PC:%xn», this. PC));
s.append (String.format («rgCOM:%xn», this. rgCOM));
s.append (String.format («ноль отрицательный переполнение:%d %d %dn» ,
this.zero,
this.negative,
this.overflow));
return s. toString ();
}
}
Листинг класса ALU:
public class ALU {
int reg;
Memory ram;
Processor proc;
public ALU (Memory ram, Processor proc) {
this.ram = ram;
this.proc = proc;
}
/*
* 0 — все хорошо
* 1 — переполнение стека
* 2 — неизвестная команда
*/
public int execute (int com) {
int t;
switch (com) {
//ADD
case 0:
t = proc.stack.peek ()+ram.read (reg);
setFlags (t);
ram.write (t & 0xFF, reg);
break;
//ADC
case 1:
t = proc.stack.peek ()+ram.read (reg)+proc.overflow;
setFlags (t);
ram.write (t & 0xFF, reg);
break;
//SUB
case 2:
t = proc.stack.peek ()-ram.read (reg);
setFlags (t);
ram.write (t & 0xFF, reg);
break;
//SUB
case 3:
t = proc.stack.peek ()-ram.read (reg)-proc.overflow;
setFlags (t);
ram.write (t & 0xFF, reg);
break;
//AND
case 4:
t = proc.stack.peek () & ram. read (reg);
setFlags (t);
ram.write (t & 0xFF, reg);
break;
//OR
case 5:
t = proc.stack.peek () | ram. read (reg);
setFlags (t);
ram.write (t & 0xFF, reg);
break;
//NOT
case 6:
t = proc.stack.pop ();
t = (~t) & 0xFF;
setFlags (t);
proc.stack.push (t);
break;
//PUSH
case 7:
if (!proc.stack.push (ram.read (reg))) {
return 1;
}
break;
//POP
case 8:
ram.write (proc.stack.pop (), reg);
break;
//INC
case 9:
t = proc.stack.pop ();
t++;
setFlags (t);
proc.stack.push (t);
break;
//DEC
case 10:
t = proc.stack.pop ();
t—;
setFlags (t);
proc.stack.push (t);
break;
//JMP
case 11:
proc.PC = reg;
break;
//JNZ
case 13:
if (proc.zero == 0) proc. PC = reg;
break;
//CALL
case 14:
t = proc. PC-2;
proc.PC = reg;
if (!(proc.stack.push (t & 0xFF) &&
proc.stack.push (t & 0xF00))) {
return 1;
}
break;
//RET
case 15:
int t1 = proc.stack.pop ();
int t2 = proc.stack.pop ();
t = t1 & t2;
proc.PC = t;
break;
default:
return 2;
}
return 0;
}
private void setFlags (int r)
proc.overflow = (r < 0
}
Листинг класса Stack:
public class Stack {
int[] s;
int max;
int size;
public Stack (int n) {
s = new int[n];
for (int i = 0; i < n; ++i) {
s[i] = -1;
max = n;
size = 0;
}
}
/*
* значение верхнего элемента
*/
public int peek () {
return s[0];
}
/*
* добавить элемент в стек
*/
public boolean push (int x) {
if (size+1 > max) return false; // переполнение
for (int i = size; i > 0; i—) {
s[i] = s[i-1];
}
s[0] = x;
size++;
return true;
}
/*
* вытащить верхний элемент
*/
public int pop () {
int t = s[0];
for (int i = 1; i < max; i++) {
s[i-1] = s[i];
}
size—;
return t;
}
}
Листинг класса Memory:
class Memory {
private int[] ram;
public Memory () {
ram = new int[4096];
}
public int size () {
return ram. length;
}
public int read (int addr) {
return ram[addr];
}
public void write (int data, int addr) {
ram[addr] = data;
}
public void clear () {
for (int i = 0; i < ram. length; ++i) {
ram[i] = 0;
}
}
public String toString () {
StringBuilder b = new StringBuilder ();
int c = 128;
for (int i = 0, k = 0; i < c; i++) {
int j = 0;
while ((k = i + j*c) < ram. length) {
b.append (String.format («%2h», k));
b.append («:»);
b.append (String.format («%2h», ram[k]));
b.append (««);
j++;
}
b.append («n»);
}
return b. toString ();
}
}