| modelmachine | Loading last commit info... | |
| samples | ||
| .gitignore | ||
| .travis.yml | ||
| LICENSE.txt | ||
| Makefile | ||
| README.md | ||
| pylintrc | ||
| requirements.txt | ||
| setup.py |
modelmachine
Model machine emulator
TODO
- Проверить test.alu.swap
- Работа с плавающей запятой
- Подумать еще о mock в тестах
- Переделать документацию модулей
- Исправить опечатки в документации
- Расширить howto
Модельная машина
Модельная машина - это чистая архитектурная концепция, позволяющая понять логику функционирования центральных процессоров. По своей структуре она близка к компьютерам первого поколения. Подробнее читайте по ссылкам внизу.
Quickstart
Установка пакета происходит командой:
# python3 -m pip install --upgrade modelmachine
После этого вам становится доступна консольная команда modelmachine.
Для проверки вы можете сделать modelmachine test. Будет выполнена
серия тестов, все тесты должны закончиться успехом.
Посмотрите примеры в папке , по их образу можно начинать писать программы для модельных машин. Запуск программы делается командой:
$ modelmachine run program.mmach
Также доступна пошаговая отладка командой:
$ modelmachine debug program.mmach
Поддерживается компилирование ассемблера модельной машины (mmasm) в ее машинный код.
$ modelmachine asmg source.mmasm result.mmach
Пример
mm3
[config]
input = 0x100,0x101
output = 0x103
[code]
; x = ((a * -21) % 50 - b) ** 2 == 178929
03 0100 0005 0103 ; x := a * -21
04 0103 0006 0102 ; [0102] := x / 50, x := x % 50
02 0103 0101 0103 ; x := x - b
03 0103 0103 0103 ; x := x * x
99 0000 0000 0000 ; halt
; ---------------------
FFFFFFFFFFFFEB ; -21
00000000000032 ; 50
[input]
-123 456
- Все, что идет после символа
;- комментарий. - Первая строчка - обязательное название архитектуры. Список поддерживаемых архитектур смотри ниже.
- После этого должна идти секция
config, описывающая ячейки памяти, по которым нужно производить ввод и вывод. - После этого секция кода, содержащая набор 16-ричных чисел, записываемых в память модельной машины. Пропускать часть машинного слова нельзя. Рекомендуется писать по одному машинному слову в строке, по желанию разбивая разряды на части пробелами.
- Пустые строки игнорируются.
- После этого идет секция ввода - несколько чисел, разделенных пробельными
символами. Их число должно совпадать с числом ячеек для ввода в параметре
inputв секцииconfig. - Если секция ввода отсутствует или если она пустая, то входные данные читаются со стандартного потока ввода до исполнения программы.
- По окончании работы на экран будут напечатаны десятичные числа со знаком,
лежащие в ячейках, перечисленных в параметре
outputв секцииconfig.
Язык ассемблера
Для машины с модификаией адресов поддерживается ассемблер. Ассемблер нечувствителен к регистру. Команды компилятору предворяются точкой. Доступные команды компилятора:
.config число- дальнейшие команды и данные располагать начиная с адресачисло..code- дальше идет исходный код, который нужно располагать начиная с адреса0x00..word список_чисел- разместить список чисел в памяти как есть, каждое число занимает пару ячеек..dump имя_метки- по завершении работы программы вывести содержимое памяти по адресуимя_метки, команда выводит содержимое двух ячеек как одно число. Для вывода массива фиксированного размера используйте формат.dump имя_метки(размер). Также метки для вывода можно перечислять через запятую. Например:.dump array(5), sum
Коды команд те же, что и в таблице кодов mmm. Имя метки - последовательность
английских букв, цифр и знака _, первый символ последовательности - не цифра.
Адрес представляет собой либо имя метки, либо строку вида
имя_метки(имя_регистра), где имя_регистра - одно из значений R0-RF.
Ввод данных не поддерживается.
.config 0x100
sum: .word 0
array: .word -1, 2, 3, 4, 5
zero: .word 0
size_word: .word 2
size_array: .word 10
.dump array(5), sum
.code
load R2, size_word
load RF, size_array
load R5, zero
rsub R6, R6
rpt: add R5, array(R6)
radd R6, R2
rcomp R6, RF
jneq rpt
store R5, sum
halt
Внутреннее устройство
Данная реализация модельной машины состоит из классов, разбитых на файлы-модули:
memory.py- память; делится на два класса:регистроваяиоперативная; оперативная делится наlittle-endianиbig-endiannumeric.py- целочисленная арифметика с фиксированным числом двоичных знаковalu.py- арифметико-логическое устройство, работает с четко специализированными регистрами:R1,R2,S,FLAGSиPC.cu.py- контролирующее устройство, выполняющее считывание команд из памяти и запускающее необходимые методы в арифметико-логическом устройствеio.py- устройство ввода-выводаcpu.py- финальное объединение составляющих устройств в единое целоеasm.py- ассемблер для машины с модификацией адресов
Здесь дано поверхностное описание модулей. За более подробным обращайтесь к документации конкретных модулей и их исходным кодам.
memory.py
AbstractMemory - класс абстрактной памяти, предоставляющий интерфейс для
надежной связи частей компьютера. Основные методы: fetch и put, которые
принимают на вход адрес в памяти и количество битов, с которыми нужно работать,
количество должно быть кратно размеру ячейки (слова). Строго
рекомендуется их использовать везде.
RandomAccessMemory - класс, реализующий память прямого доступа. При
инициализации указывается размер машинного слова и количество этих слов. Если
is_protected=True, то при попытке считывания из неинициализированной ячейки
будет выброшено исключение, иначе, метод fetch вернет нуль.
RegisterMemory - класс, реализующий регистровую память. Метод add_register
добавляет регистр определенного размера или проверяет, что уже добавленный
регистр имеет правильный размер.
numeric.py
Класс Integer реализует целочисленную арифметику фиксированной длины.
Поддерживаемые операторы: +, -, *, /, %, ==, !=. Плюс методы
get_value и get_data. Округление при делении производится в сторону нуля.
alu.py
Арифметико-логическое устройство работает с четко специализированными регистрами:
R1,R2,Sдля арифметических операций.FLAGSдля хранения флагов состояния.PCтолько для пересылки туда адреса из регистраR1при условных переходах.
Схема работы:
- Арифметические команды:
add,sub,smul,sdiv,umul,udiv,sdivmod,udivmod. За исключением командdivmodарифметические команды работают следующим образом:S := R1 op R2. Плюс в зависимости от результата выставляется регистрFLAGS- комбинация флагов CF, OF, SF и ZF. addиsub- сложение и вычитание соответсвенно.smulиsdiv- знаковое умножение и деление соответсвенно.umulиudiv- беззнаковое умножение и деление соответсвенно.sdivmodиudivmod- знаковое и беззнаковое соответственно деление с остатком.S := R1 / R2; R1 := R1 % R2.- Команда пересылки
move:S := R1. - Команды безусловного перехода
jumpи условного переходаcond_jumpработают по схемеPC := R1, режим работыcond_jumpзависит от того, какие дополнительные аргументы будут переданы. - Команда останова
haltпросто выставляет флаг остановки HALT в регистре флагов
cu.py
Управляющее устройство.
AbstractControlUnit
Обычно управляющее устройство работает по схеме:
fetch_and_decode- загрузка и расшифровка очередной команды. Содержимое ячейки оперативной памяти с адресом записанным в регистреPCзагружается в регистрRI, затем из него извлекается код операции и адреса операндов, затем счетчикPCувеличивается на длину только что считанной команды.load- данные по только что считанным адресам загружаются в регистры процессораR1иR2execute- в зависимости от кода операции в арифметико-логическом устройстве запускается та или иная схема вычислений.write_back- результат вычислений записывается куда полагается (например, по одному из адресов считанных в начале).
Все эти методы поддерживаются в AbstractControlUnit.
Также в нем написаны использующие их методы, являющиеся интерфейсом
устройства управления:
step- сделать один шаг, описанный алгоритмом выше.get_status- вернуть статус процессора (выполняется/остановлен). Остановка производится командой остановаhalt.run- выполнять один шаг за другим, пока процессор не будет остановлен.
Далее, наследники AbstractControlUnit определяют первые 4 метода.
io.py
Устройство ввода-вывода.
- При инициализации устанавливается адрес с которого нужно загружать программы пользователя.
- Метод
load_sourceзагружает последовательность шеснадцатиричных слов по указанному адресу. - Метод
load_dataзагружает данные (полученные из строки чисел) по данным адресам. - Методы
get_intиput_intработают с содержимым одного слова. Размер слова устанавливается при инициализации.
cpu.py
Финальная сборка всех составных устройств в единое целое.
- Поддерживается доступ к составным устройствам. Например, доступ
к устройству ввода/вывода можно получить через
cpu.io_unit, к регистрам черезcpu.registersи так далее. load_program- загрузка программы, конфигурации и данных в оперативную память.print_result- печать результата работы программы.run_file- загрузка, исполнение и печать результата.
Поддерживаемые архитектуры
Как добавить новую архитектуру?
- Выписать все команды устройстройства и их коды.
- Добавить их в таблицу ниже и описание еще ниже.
- Добавить новый класс устройства управления на основе существующего.
- Добавить новый класс CPU использующий это устройство управления,
добавить этот класс в список
CPU_LISTв файлеcpu.py. - Добавить тесты для обоих этих классов в файлы
tests/test_cu.pyиtests/test_cpu.py. - Добавить примеры в папку
samples. - Прислать pull request.
Таблица команд модельных машин
| OPCODE | mm-3 | mm-2 | mm-v | mm-1 | mm-m |
|---|---|---|---|---|---|
| 0x00 | move | move | move | load | load |
| 0x01 | add | add | add | add | add |
| 0x02 | sub | sub | sub | sub | sub |
| 0x03 | smul | smul | smul | smul | smul |
| 0x04 | sdiv | sdiv | sdiv | sdiv | sdiv |
| 0x05 | comp | comp | comp | comp | |
| 0x13 | umul | umul | umul | umul | umul |
| 0x14 | udiv | udiv | udiv | udiv | udiv |
| 0x10 | store | store | |||
| 0x20 | swap | move | |||
| 0x21 | radd | ||||
| 0x22 | rsub | ||||
| 0x23 | rsmul | ||||
| 0x24 | rsdiv | ||||
| 0x25 | rcomp | ||||
| 0x33 | rumul | ||||
| 0x34 | rudiv | ||||
| 0x80 | jump | jump | jump | jump | jump |
| 0x81 | jeq | jeq | jeq | jeq | jeq |
| 0x82 | jneq | jneq | jneq | jneq | jneq |
| 0x83 | sjl | sjl | sjl | sjl | sjl |
| 0x84 | sjgeq | sjgeq | sjneq | sjgeq | sjgeq |
| 0x85 | sjleq | sjleq | sjleq | sjleq | sjleq |
| 0x86 | sjg | sjg | sjg | sjg | sjg |
| 0x93 | ujl | ujl | ujl | ujl | ujl |
| 0x94 | ujgeq | ujgeq | ujgeq | ujgeq | ujgeq |
| 0x95 | ujleq | ujleq | ujleq | ujleq | ujleq |
| 0x96 | ujg | ujg | ujg | ujg | ujg |
| 0x99 | halt | halt | halt | halt | halt |
На самом деле операция div запускает в АЛУ схему divmod.
Ниже дана таблица команд условных переходов. Откуда берутся операнды для сравнения зависит от архитектуры модельной машины. Подробнее смотри [1].
| Мнемонический код | Условие перехода | Расшифровка/описание |
|---|---|---|
| jeq | == | jump if equal |
| jneq | != | jump if not equal |
| sjl | < s | signed jump if less |
| sjgeq | >= s | signed jump if greater or equal |
| sjleq | <= s | signed jump if less or equal |
| sjg | > s | signed jump if greater |
| ujl | < u | unsigned jump if less |
| ujgeq | >= u | unsigned jump if greater or equal |
| ujleq | <= u | unsigned jump if less or equal |
| ujg | > u | unsigned jump if greater |
mm-3
Архитектура трехадресной модельной машины.
- Размер ячейки оперативной памяти: 7 байт.
- Размер адреса: 2 байта.
- Арифметические вычисления производятся с одной ячейкой оперативной памяти.
- Код команды помещается в одну ячейку оперативной памяти
КОП А1 А2 А3. - Регистры:
S,R1,R2,FLAGS,PC,RI,ADDR.
Назначение регистров:
S- регистр сумматор, в него записывается результат арифметической операции.R1,R2- регистры операндов арифметических операций.FLAGS- регистр флагов.PC- регистр указатель инструкции.RI- регистр для хранения инструкции.ADDR- регистр для хранения адреса для инструкции перехода.
Действия процессора для арифметических инструкций (add, sub,
smul, sdiv, umul, udiv) КОП A1 A2 A3:
- Загрузить содержимое ячейки оперативной памяти с адресом
А1в регистрR1(R1 := [A1]). - Загрузить содержимое ячейки оперативной памяти с адресом
А2в регистрR2(R2 := [A2]). - Запустить в АЛУ схему, реализующую операцию, задаваемую
КОП. - Записать результат из регистра
Sв ячейку оперативной памяти с адресомА3. Если выполняется операция деления, в оперативную память записываются два результата: частное – в ячейку с адресомА3, остаток – в следующую ячейку, по адресу(А3+1) mod 16^4.
jump A1 A2 A3:PC := A3- Условные переходы: сравниваются
R1иR2, в зависимости от результата происходитPC := A3. - Команда пересылки
move: [A3] := R1.
mm-2
Архитектура двухадресной модельной машины.
- Размер ячейки оперативной памяти: 5 байт.
- Размер адреса: 2 байта.
- Арифметические вычисления производятся с одной ячейкой оперативной памяти.
- Код команды помещается в одну ячейку оперативной памяти
КОП А1 А2. - Регистры:
R1,R2,FLAGS,PC,RI,ADDR.
Действия для арифметических команд add, sub, smul, sdiv, umul,
udiv:
R1 := [A1], R2 := [A2]R1 := R1 op R2[A1] := R1
Действия для команды сравнения cmp:
R1 := [A1], R2 := [A2]- Запустить в АЛУ схему
sub, выставить регистрFLAGS
jump A1 A2:PC := A2- Условные переходы делаются исходя из регистра
FLAGS move A1 A2:[A1] := [A2]- Команда останова
haltвзводит флагHALTв регистреFLAGS
mm-v
Архитектура модельной машины с переменным (variable) фарматом команд.
- Размер ячейки оперативной памяти: 1 байт.
- Размер адреса: 2 байта.
- Арифметические вычисления производятся со словами в 5 ячеек оперативной памяти.
- Код команды занимает разное количество ячеек в зависимости от выполняемой операции.
- Регистры:
R1,R2,FLAGS,PC,RI,ADDR.
Таблица кодов команд:
| Код команды | Мнемоник | Формат | Длина (в байтах) |
|---|---|---|---|
| 0x00 | move | move A1 A2 | 5 |
| 0x01 | add | add A1 A2 | 5 |
| 0x02 | sub | sub A1 A2 | 5 |
| 0x03 | smul | smul A1 A2 | 5 |
| 0x04 | sdiv | sdiv A1 A2 | 5 |
| 0x05 | comp | comp A1 A2 | 5 |
| 0x13 | umul | umul A1 A2 | 5 |
| 0x14 | udiv | udiv A1 A2 | 5 |
| 0x80 | jump | jump A1 | 3 |
| 0x81 | jeq | jeq A1 | 3 |
| 0x82 | jneq | jneq A1 | 3 |
| 0x83 | sjl | sjl A1 | 3 |
| 0x84 | sjgeq | sjgeq A1 | 3 |
| 0x85 | sjleq | sjleq A1 | 3 |
| 0x86 | sjg | sjg A1 | 3 |
| 0x93 | ujl | ujl A1 | 3 |
| 0x94 | ujgeq | ujgeq A1 | 3 |
| 0x95 | ujleq | ujleq A1 | 3 |
| 0x96 | ujg | ujg A1 | 3 |
| 0x99 | halt | halt | 1 |
Действия для арифметических команд add, sub, smul, sdiv, umul,
udiv:
R1 := [A1], R2 := [A2]R1 := R1 op R2[A1] := S
Действия для команды сравнения cmp:
R1 := [A1], R2 := [A2]- Запустить в АЛУ схему
sub, выставить регистрFLAGS
jump A1:PC := A1- Условные переходы делаются исходя из регистра
FLAGS move A1 A2:[A1] := [A2]- Команда останова
haltвзводит флагHALTв регистреFLAGS
mm-1
Архитектура одноадресной модельной машины.
- Размер ячейки оперативной памяти: 3 байта.
- Размер адреса: 2 байта.
- Арифметические вычисления производятся с одной ячейкой оперативной памяти.
- Код команды помещается в одну ячейку оперативной памяти
КОП А. - Регистры:
S,R,S1,FLAGS,PC,RI.
Регистры S и S1 хранят информацию постоянно, а не затираются при
выполнении очередной команды, как было раньше. В регистр R закгружается
операнд для арифметических операций.
Действия для арифметических команд (исключая деление) add, sub, smul,
umul:
R := [A]S := S op R
Действия для команд деления sdivmod, udivmod:
R := [A]S := S / R; S1 := S % R
Действия для команды сравнения cmp:
R := [A]- Запустить в АЛУ схему
sub, выставить регистрFLAGS
jump A:PC := A- Условные переходы делаются исходя из регистра
FLAGS load A:S := [A]store A:[A] := Sswap:S, S1 := S1, S- Команда останова
haltвзводит флагHALTв регистреFLAGS
mm-m
Архитектура модельной машины с модификацией адресов (modification).
- Размер ячейки оперативной памяти: 2 байта.
- Размер адреса: 2 байта.
- Арифметические вычисления производятся со словом в 4 байта.
- Код команды занимает разное количество ячеек в зависимости от выполняемой
операции. Арифметические команды имеют формы регистр-регистр и регистр-память.
Команды регистр-регистр имеют формат
КОП RA1 RA2и занимают 2 байта. Команды регистр-память имеют форматКОП R M Aи занимают 4 байта. Команды перехода имеют форматКОП 0 0 Aи занимают 4 байта. - Регистры:
R0-RF,S,RZ,FLAGS,PC,RI.
Основное отличие этой машины от предыдущих - наличие адресуемых регистров
общего назначения R0-RF, используемых для арифметических
вычислений и адресации памяти. S, RZ - неадресуемые регистры для работы
АЛУ.
Также адресация данных теперь производится таким алгоритмом:
- Возьмем содержимое адресуемого регистра с номером
M(от 0x0 до 0xF):[M]. Если номер регистраMравен нулю, значение[M]также равно нулю вне зависимости от содержимого регистраR0. - Добавим к нему адрес
A(от 0x0000 до 0xFFFF):[M] + A. - Возьмем остаток от деления этого адреса на 2^16:
([M] + A) % 2^16. - Возьмем из ОЗУ данные по полученному адресу:
[[M] + A].
Таблица кодов команд:
| Код команды | Мнемоник | Формат | Длина (в байтах) |
|---|---|---|---|
| 0x00 | load | load R M A | 4 |
| 0x01 | add | add R M A | 4 |
| 0x02 | sub | sub R M A | 4 |
| 0x03 | smul | smul R M A | 4 |
| 0x04 | sdiv | sdiv R M A | 4 |
| 0x05 | comp | comp R M A | 4 |
| 0x11 | addr | addr R M A | 4 |
| 0x13 | umul | umul R M A | 4 |
| 0x14 | udiv | udiv R M A | 4 |
| 0x10 | store | store R M A | 4 |
| 0x20 | rmove | rmove RX RY | 2 |
| 0x21 | radd | radd RX RY | 2 |
| 0x22 | rsub | rsub RX RY | 2 |
| 0x23 | rsmul | rsmul RX RY | 2 |
| 0x24 | rsdiv | rsdiv RX RY | 2 |
| 0x25 | rcomp | rcomp RX RY | 2 |
| 0x33 | rumul | rumul RX RY | 2 |
| 0x34 | rudiv | rudiv RX RY | 2 |
| 0x80 | jump | jump 0 M A | 4 |
| 0x81 | jeq | jeq 0 M A | 4 |
| 0x82 | jneq | jneq 0 M A | 4 |
| 0x83 | sjl | sjl 0 M A | 4 |
| 0x84 | sjgeq | sjgeq 0 M A | 4 |
| 0x85 | sjleq | sjleq 0 M A | 4 |
| 0x86 | sjg | sjg 0 M A | 4 |
| 0x93 | ujl | ujl 0 M A | 4 |
| 0x94 | ujgeq | ujgeq 0 M A | 4 |
| 0x95 | ujleq | ujleq 0 M A | 4 |
| 0x96 | ujg | ujg 0 M A | 4 |
| 0x99 | halt | halt 00 | 2 |
Действия для загрузки исполнительного адреса в регистр addr R M A:
S := [M] + AR := S
Действия для арифметических команд регистр-память (исключая деление) add,
sub, smul, umul (формат op R M A):
S, RZ := R, [[M] + A]S := S op RZR := S
Действия для команд деления регистр-память sdivmod и udivmod
(формат op R M A, R_next - регистр, следующий за регистром R):
S, RZ := S, [[M] + A]S, RZ := S / RZ, S % RZR, R_next := S, RZ
Действия для команды сравнения comp R M A:
S, RZ := S, [[M] + A]- Запустить в АЛУ схему
sub S RZ, выставить регистрFLAGS
Действия для команды загрузки load R M A:
R := [[M] + A]
Действия для команды выгрузки store R M A:
[[M] + A] := R
Действия для арифметических команд регистр-регистр (исключая деление) radd,
rsub, rsmul, rumul (формат op RX RY):
S, RZ := RX, RYS := S op RZRX := S
Действия для команд деления регистр-регистр rsdiv и rudiv
(формат - op RX RY; RX_next - регистр, следующий за регистром RX, если
RX = RF, то RX_next = R0):
S, RZ := RX, RYS, RZ := S / RZ, S % RZRX, RX_next := S, RZ
Действия для команды сравнения rcomp RX RY:
S, RZ := RX, RY- Запустить в АЛУ схему
sub S RZ, выставить регистрFLAGS
jump 00 A:PC := A- Условные переходы делаются исходя из регистра
FLAGS - Команда останова
haltвзводит флагHALTв регистреFLAGS
References
- E. А. Бордаченкова - "Модельные ЭВМ" http://al.cs.msu.su/files/ModComp.pdf
- Е. А. Бордаченкова - "Архитектура ЭВМ. Учебные машины. Методическое пособие" http://al.cs.msu.su/files/bordachenkova.architecture.model.machines.2010.doc
- В. Г. Баула - "Введение в архитектуру ЭВМ и системы программирования" http://arch.cs.msu.ru/Page2.htm
- http://cmcmsu.no-ip.info/1course/um3.command.set.htm