Разработка программы-эмулятора для заданной гипотетической ЭВМ
В настоящее время получило широкое распространение использование микропроцессоров в качестве встроенных элементов систем автоматического управления, в том числе как управляющих блоков периферийных узлов вычислительных комплексов. Функции, возлагаемые в такой системе на микропроцессор, полностью определяются программой, хранимой в его памяти. Поскольку программа работы встроенного микропроцессора изменяется сравнительно редко, к параметрам этой программы и к надежности ее функционирования предъявляются особенно жесткие требования. В связи с этим являются обязательными тщательная отладка и всестороннее тестирование программы работы микропроцессора.
Микропроцессоры имеют, как правило, ограниченные возможности ввода-вывода программ и обрабатываемых данных, что затрудняет выполнение работ по отладке программ. Такие работы также затруднены или вообще невозможны при разработке новых микропроцессорных систем. Всё это стимулирует использование универсальных ЭВМ для отладки и тестирования программ микроЭВМ и микропроцессоров, т.е. использование методики эмуляции программ.
Темой курсовой работы является разработка программы-эмулятора для заданной гипотетической ЭВМ, в упрощенном виде воссоздающей структуру реальной машины. Эмуляция выполняется на ПЭВМ в операционной среде MS DOS, рабочим языком программирования является язык Турбо Паскаль.
1. СОДЕРЖАНИЕ КУРСОВОЙ РАБОТЫ
Тематика курсовых работ связана с созданием программ-эмуляторов для учебных ЭВМ со сравнительно простой структурой. Студенту выдается функциональная схема ЭВМ и набор команд, выполняемых данной машиной. После изучения принципа работы ЭВМ и порядка выполнения её команд студент разрабатывает программную модель на языке Турбо Паскаль и проверяет её работу на тестовых примерах. Такими примерами служат небольшие программы в машинных кодах учебной ЭВМ, организующие вычисления по формулам, согласованным с преподавателем.
По окончании выполнения курсовой работы студент оформляет отчет в соответствии с требованиями, изложенными в настоящих методуказаниях, и защищает его.
График выполнения курсовой работы и содержание отчета регламентируются техническим заданием, которое подписывается студентом и утверждается руководителем курсовой работы.
Основные этапы разработки программы-эмулятора детально описаны ниже на примере гипотетической ЭВМ, которой условно присвоено название М1. Внимательное изучение предлагаемой методики является обязательным для успешного выполнения курсовой работы. При этом следует иметь в виду, что структуры ЭВМ, выдаваемые студентам в техническом задании к курсовой работе, могут существенно отличаться от структуры машины М1, поэтому механический перенос фрагментов эмулятора М1 на другие структуры ЭВМ в общем случае невозможен.
2 УЧЕБНАЯ ВЫЧИСЛИТЕЛЬНАЯ МАШИНА М1
2.1 Архитектура ЭВМ М1
Структурная схема ЭВМ М1 приведена на рис.1. В состав ЭВМ входят следующие узлы:
- оперативная память MEMORY емкостью 256 8-разрядных ячеек, предназначенная для хранения команд и данных, используемых в программе;
- восьмиразрядный счетчик адреса команд SAK, предназначенный для хранения адреса следующей команды;
- восьмиразрядный регистр адреса RA, определяющий адрес ячейки памяти, в которую записывается или из которой читается информация;
- восьмиразрядный буферный регистр слова RS для временного хранения читаемой или записываемой информации;
- шестнадцатиразрядный регистр команд RK, хранящий выполняемую команду;
- сумматор адреса SA, предназначенный для формирования исполнительного адреса операнда;
- шестнадцатиразрядные операционные регистры OR1 и OR2 для временного хранения операндов;
- блок из четырех шестнадцатиразрядных регистров R с номерами 0, 1, 2, 3;
- арифметико-логическое устройство АЛУ, выполняющее операцию, заданную кодом операции команды;
- шестнадцатиразрядный регистр результата RES, предназначенный для временного хранения результата операции, выполняемой АЛУ;
- дешифратор кода операции DC, осуществляющий настройку АЛУ на выполнение очередной команды;
- четырехразрядный регистр признаков RP.
При выполнении арифметической операции первый бит RP устанавливается в «1», если ее результат равен нулю; второй бит устанавливается в «1» при отрицательном, а третий бит – при положительном результате этой операции; четвертый бит устанавливается в «1» в случае некорректности выполнения операции (переполнение регистра результатов RES или попытка деления на нуль).
Для логических операций и для команд сдвига производится лишь анализ результата на равенство нулю: RP[1] = 1, если результат операции равен нулю; RP[2] = 1, если этот результат не равен нулю.
В состав команды входят следующие поля:
- код выполняемой операции KOP (биты 1 – 4);
- модификатор адреса МА (биты 5 и 6);
- номер регистра NR (биты 7 и 8);
- адрес ячейки памяти AM (биты 9 – 16).
Адреса ячеек изменяются от 0 до 255 (от 00000000 до 11111111 в двоичной системе счисления).
Обрабатываемыми данными для арифметических операций являются 16-разрядные целые числа, причем первый разряд определяет знак числа, остальные – двоичные цифры числа. Отрицательные числа представлены в дополнительном коде. В логических операциях также используются 16-разрядные операнды, которые рассматриваются как последовательности битов.
Команды программы и обрабатываемые данные занимают две смежные ячейки памяти, при этом адрес первой ячейки всегда должен быть четным.
В качестве первого операнда в двухместных операциях принимается содержимое регистра R, номер которого определяется значением поля NR регистра команд RK. Формирование второго операнда зависит от типа адресации, определяемой значением модификатора адреса МА:
МА = 00
= АМ – прямая адресация;
MA = 01
= MEMORY(AM) – косвенная адресация;
МА = 10 OР2 = AM – непосредственная адресация.
МА = 11
= АМ +(
) – индекс-адресация;
Здесь ОР2 – условное обозначение второго операнда, которым в данном случае является непосредственно значение поля АМ регистра команд RK, а не содержимое ячейки памяти с адресом АМ.
В качестве индексного регистра всегда используется нулевой регистр блока R.
При прямой, косвенной и индекс-адресации , где исполнитеьный адрес
определяет адрес левого байта поля длиной два байта. При непосредственной адресации операнды имеют длину один байт, первый операнд размещается в битах 9 – 16 регистра OR1, второй операнд – в битах 9 – 16 регистра OR2. При этом биты 1 – 8 регистров OR1 и OR2 должны быть нулевыми.
2.2 Выполнение команд в ЭВМ M1
Система команд ЭВМ M1, приведенная в табл.1, включает в себя:
- команды выполнения операций с блоком регистров («Загрузка операнда» и «Сохранение операнда»);
- арифметико-логические команды («Сложение», «Вычитание», «Умножение», «Деление», «Сравнение», «Конъюнкция», «Дизъюнкция», «Сложение по модулю 2»);
- команды сдвига («Сдвиг влево логический» и «Сдвиг вправо логический»);
- команды управления порядком выполнения программы («Переход», «Вызов подпрограммы», «Возврат из подпрограммы», «Останов»).
Таблица 1. Система команд ЭВМ М1
Код опер. | Наименование | Признаки | Выполнениекоманды | |||
= | < | > | нк | |||
0000 | Останов | – | – | – | – | |
0001 | Загрузка операнда | + | + | + | – | |
0010 | Сохранение операнда | – | – | – | – | |
0011 | Сложение | + | + | + | + | |
0100 | Вычитание | + | + | + | + | |
0101 | Умножение | + | + | + | + | |
0110 | Деление | + | + | + | + | |
0111 | Конъюнкция | + | + | – | – | |
1000 | Дизъюнкция | + | + | – | – | |
1001 | Сложение по модулю 2 | + | + | – | – | |
1010 | Сдвиг влево логический | + | + | – | – | |
1011 | Сдвиг вправо логический | + | + | – | – | |
1100 | Переход | – | – | – | – | См.описание |
1101 | Сравнение | + | + | + | – | |
1110 | Обращение к подпрограмме | – | – | – | – | |
1111 | Возврат из подпрограммы | – | – | – | – | |
Круглые скобки в графе «Выполнение команды» означают содержимое регистра или ячейки памяти. Например, запись – это адрес ячейки, а (
) – содержимое ячейки с адресом
.
В табл.1 - это i – ый регистр блока R, ОР2 – второй операнд, формирование которого зависит от вида адресации.
Команда “Загрузка операнда” пересылает содержимое двух байт оперативной памяти, адрес которых задан значением , в регистр
, номер которого определяется значением NR. При непосредственной адресации эта команда посылает в биты 9 – 16 регистра
(i = 0 .. 3) значение компонента AM регистра команд RK, при этом биты 1 .. 8 регистра
обнуляются. Команда «Сохранение операнда» осуществляет обратную операцию – пересылку данных из регистра
в память. При выполнении команды «Загрузка операнда» вырабатывается признак результата, определяющий содержимое соответствующих разрядов регистра RP: RP[1] = 1, если пересылаемое значение равно нулю; RP[2] = 1, если это значение меньше нуля; RP[3] = 1, если оно больше нуля.
В арифметико-логических командах первый операнд выбирается из регистра , а расположение второго операнда определяется полями МА и АМ регистра RK. Признаки завершения операции, которые вырабатываются при выполнении рассматриваемых команд, представлены в табл.1. В частности, признак «нк» (некорректность операции) означает, что в результате выполнения данной операции возникает переполнение регистра результатов Res или же здесь отмечена попытка деления на нуль.
Символ “–” в графе признаков табл.1 определяет ситуацию, которая не может возникнуть в данной операции (например, некорректность при выполнении операции сдвига), или то, что данная операция не изменяет регистр RP (например, команда перехода).
Сущность изменения регистра RP рассмотрим на конкретном примере. Предположим, что в программе выполнена команда сложения и при этом получен положительный результат. Тогда в состояние «1» должен быть установлен лишь третий бит регистра RP, остальные биты должны быть сброшены на нуль.
Примечание. Если в команде, приведенной в табл.1, все графы признаков отмечены символом “–”, то это означает лишь то, что при этом сохраняются предыдущие значения признаков (но не выполняется сброс их на нуль).
Некоторую специфику в данной группе имеет команда сравнения. При ее выполнении вырабатываются только признаки завершения операции: RP[1] = 1, если сравниваемые операнды равны друг другу; RP[2] = 1, если первый операнд меньше второго; RP[3] = 1, если первый операнд больше второго операнда. Содержимое регистра RES при этом не изменяется.
В командах логического сдвига первый операнд ОР1 выбирается из регистров , пересылается в регистр OR1, сдвигается влево или вправо, а затем через регистр RES пересылается на тот же регистр
. Освобождаемые при выполнении операции разряды в левой или в правой части регистра OR1 заполняются нулями. Количество сдвигов определяется операндом ОР2, поступающим на регистр OR2. В зависимости от типа адресации в качестве операнда ОР2 берется исполнительный адрес
или значение АМ при непосредственной адресации. Если ОР2 > 15, то сдвиг регистра OR1 не производится, но во все его разряды записываются нули.
В команде перехода биты 5 – 8 – это поле маски. Если логическое произведение содержимого маски и содержимого регистра признаков отличается от нуля, то осуществляется переход по адресу , т.е. следующей исполняемой командой программы будет команда с адресом
. Если маска М=1111, то управление передается по адресу
вне зависимости от содержимого регистра RP (безусловный переход). Если М = 0000, то выполняется следующая команда (команда перехода в этом случае эквивалентна пустой команде). В команде перехода всегда используется косвенная адресация, т.е.
= MEMORY(AM).
Частные случаи значения маски:
- М = 0000 - пустая команда (нет передачи управления);
- М = 1000 - условный переход по нулю;
- М = 0010 - условный переход по «больше»;
- М = 1100 - условный переход по нулю или по «меньше»;
………………….
- М = 1111 - безусловный переход.
Значение М = 0001 определяет переход по признаку некорректности. В этом случае необходимо по адресу АМ перейти на вывод сообщения об аварийном прерывании, указав при этом код операции и адрес команды, вызвавшей прерывание, после чего произвести останов ЭВМ.
По команде «Обращение к подпрограмме» содержимое счетчика адреса команд SAK (адрес следующей команды) временно запоминается в регистре в соответствии с номером NR, а в регистр SAK загружается значение исполнительного адреса
. Тогда следующей исполняемой командой будет команда с адресом
(в данном случае команда входа в подпрограмму).
При выполнении команды возврата из подпрограммы в регистр SAK переписывается содержимое регистра . Эта команда ставится последней в подпрограмме и обеспечивает возврат на продолжение работы основной программы. В данной команде биты 9 – 16 не используются.
В команде останова принимается во внимание лишь код операции. Значения битов 5 – 16 на выполнение команды влияния не оказывают.
В системе команд ЭВМ М1 имеется ограничение на использование непосредственной адресации операндов. Этот вид адресации неприменим для команд 0010, 1100, 1110, 1111 («Запись в память», «Переход», «Вызов подпрограммы», «Возврат из подпрограммы»).
Выполнение машинной программы начинается с команды, адрес которой определяется значением пускового адреса, загруженного в регистр SAK. В дальнейшем содержимое этого регистра указывает адрес каждой очередной выполняемой команды программы. Нормальное завершение работы машинной программы имеет место при достижении команды останова. Если при выполнении какой-либо команды выработан признак некорректности (RP[4] = 1), то это рассматривается как аварийное завершение работы программы; при этом работа программы прерывается, а на экран должно быть выведено сообщение о причине прерывания.
Выполнение любой команды начинается с загрузки содержимого поля памяти, адрес которого задан регистром RK, через буферный регистр RS в регистр команд RK. Для подготовки выборки следующей команды содержимое регистра SAK увеличивается на 2 (каждая команда ЭВМ М1 занимает две смежные ячейки памяти). После этого из регистра RK выделяются поля KOP, MA, NR, AM. Дешифратор DC анализирует значение кода операции и определяет команду, которая должна выполняться в данный момент (количество входов дешифратора равно количеству разрядов в КОР, количество выходов равно количеству машинных команд).
Примечание.
Если КОП имеет m разрядов, то дещифратор должен иметь выходов, что однозначно определяет возможное максимальное количество команд данной ЭВМ.
Следующим этапом является формирование исполнительного адреса, если он используется в данной команде. При этом анализируется значение модификатора адреса МА.
Если МА = 00 (прямая адресация), то содержимое поля АМ регистра команд RK передается в регистр адреса RA.
Если МА = 01 (косвенная адресация), то в регистр адреса RA посылается значение АМ+1 (это местонахождение адресной части команды с адресом АМ), по этому адресу выбирается ячейка памяти, ее содержимое поступает в буферный регистр RS, а затем переписывается в регистр RA.
Если МА = 11 (индекс-адресация), значение адреса АМ суммируется в SA c содержимым регистра , результат суммирования передается в регистр RA. По сформированному значению содержимого регистра RA из памяти последовательно выбираются ячейки с адресами
и
+1, содержимое которых затем через регистр RS записывается соответственно в разряды 1 – 8 и 9 – 16 регистра OR2.
При МА = 10 (непосредственная адресация) содержимое поля АМ регистра RK записывается в разряды 9 – 16 регистра OR2. Значение первого операнда из регистра пересылается в регистр OR1. Результат операции формируется в регистре RES и, если это предусмотрено кодом операции, пересылается в тот же регистр
.
2.3. Составление программы на машинном языке ЭВМ M1
На первом этапе выполнения курсовой работы необходимо составить три – четыре программы на машинном языке заданной ЭВМ таким образом, чтобы в этих программах были отражены все команды ЭВМ и особенности их выполнения, в частности типы адресации операндов. В последующем эти программы будут использованы как тестовый материал для проверки работы спроектированного эмулятора.
Машинную программу целесообразно сначала записать в условных кодах, что обеспечивает более высокий уровень ее читаемости и уменьшает количество возможных ошибок. При программировании в условных кодах вместо двоичных кодов машинных операций и типов адресации записывают их символические обозначения, вместо двоичного номера регистра – его десятичный номер, вместо реального двоичного адреса операнда – символическое обозначение этого операнда.
Описанная выше методика близка к методике обозначений в языке Ассемблер. Например, команда сложения могла бы иметь следующий вид:
Здесь
- Add – условное обозначение кода операции;
- I – указание на индекс-адресацию;
- 2 – номер регистра для первого операнда;
- X – символический адрес ячейки памяти, в которой содержится переменная Х.
Тем не менее указанный способ содержит определенные недостатки, если рассматривать его применительно к разработке эмулятора. Программа на Ассемблере, символически отображающая структуры машинных команд, удобна при ее разработке. Однако дальнейшую работу, в частности преобразование в машинные команды и распределение памяти для команд, переменных и констант, выполняет компилятор. В то же время при разработке эмулятора эту работу приходится выполнять вручную. Поэтому представляется более целесообразным использовать символические обозначения машинных команд, наиболее близкие к структуре этих команд, что облегчает в дальнейшем их окончательное преобразование.
В ЭВМ М1 машинная команда разделяется на 4 поля: код операции, модификатор адреса, номер регистра и адрес операнда.
Код операции целесообразно представлять символически, используя обозначения, которые применяются в языке Ассемблера. В данном случае можно принять:
Halt – останов (код 0000);
Load – загрузка операнда (код 0001);
St – сохранение операнда (код 0010);
Add – сложение (код 0011);
Sub – вычитание (код 0100);
Mul – умножение (код 0101);
Div – деление (код 0110);
And – конъюнкция (код 0111);
Or – дизъюнкция (код 1000);
Xor – сложение по модулю 2 (код 1001);
ShL – сдвиг влево логический (код 1010);
ShR – сдвиг вправо логический (код 1011);
Jump – переход (код 1100);
Cmp – сравнение (код 1101);
Call – вызов подпрограммы (код 1110);
Ret – возврат из подпрограммы (код 1111).
Модификатор адреса имеет лишь 4 значения: 00 (прямая адресация), 01 (косвенная адресация), 10 (непосредственная адресация) и 11 (индекс-адресация). Их нет смысла заменять символическими именами.
Аналогичная ситуация имеет место с номерами регистров, также имеющими 4 значения: 00, 01, 10 и 11.
В команде перехода четырехразрядную маску целесообразно изображать в виде двух групп по два разряда аналогично командам, содержащим модификатор адреса и номер регистра.
Обозначение адреса операнда зависит от вида адресации. При непосредственной адресации значением операнда является 8-разрядное целое неотрицательное число от 0 до 255. Его целесообразно записывать в 10 с/с. При прямой и индекс-адресации нужно указывать адрес операнда. Для этого целесообразно использовать обозначение < x >, определяющееадрес поля памяти, где расположена переменная х.
Примечание. Если константа в программируемом выражении превышает значение 255 (максиально возможное значение при непосредственной алресации ) и равна, например, 310, то соответствующий операнд следует обозначить <310>, что по форме записи будет отличаться от обозначений операнда при непосредственной адресации, т.е. <310> – это не число со значением 310, а адрес ячейки памяти, в которой записано такое число.
Каждая машинная команда имеет конкретный адрес в оперативной памяти, причем этот адрес должен быть четным. Поскольку в командах перехода и вызова подпрограммы используется адрес машинной команды, то слева от символической записи каждой команды будем указывать ее адрес. Тогда приведенная выше команда сложения будет иметь вид
k+10 Add 11 10 < x >,
где k – пусковой адрес программы.
Специфику обозначения косвенной адресации рассмотрим на примере команды перехода.
В соответствии с описанием ЭВМ М1 операндом в команде перехода яввляется адрес поля памяти, в котором записан адрес перехода. Предположим, что при реализации разветвляющейся программы необходимо осуществить безусловный переход на машинную команду с адресом k+20. Этот адрес необходимо предварительно записать в какую-либо конкретную ячейку памяти, например, в ячейку k+40, расположенную вне исполняемых команд, во всяком случае после команды останова. Тогда подготовку адреса перехода можно осуществить следующим образом:
Load 10 11 k+20 { непосредственная адресация, }
{ регистр R3 }
St 00 11 k+40 { прямая адресация, регистр R3 }
Команда загрузки Load, в которой задана непосредственная адресация, записывает в регистр значениеадреса перехода, после чего команда сохранения операнда St с прямой адресацией пересылает этот адрес из регистра
в поле памяти с адресом k+40 (здесь имеется в виду, что адрес k+40 находится вне пределов адресов исполняемых команд).
Тогда команда безусловного перехода будет иметь вид
Примечание. Здесь уместно напомнить, что записанная выше команда, имея в своем описании косвенную адресацию, выполняет передачу управления не по адресу k+40, а по адресу k+20.
Аналогичным образом подготавливается косвенная адресация и для других машинных команд, но в этом случае модификатор адреса должен иметь значение 01.
Рассмотрим теперь примеры, аналогичные тестам 1, 2 и 3, приведенным в приложении 2 методических указаний.
Пример 1.
Результат решения должен быть
k+0 Load 00 01 < a >
k+2 Sub 10 01 1
k+4 St 00 01 P1
k+6 Load 00 01 < a >
k+8 Mul 00 01 <330>
k+10 St 00 01 P2
k+12 Load 00 01 < b >
k+14 Mul 10 01 28
k+16 Add 00 01 P2
k+18 Div 00 01 P1
k+20 St 00 01 P1
k+22 Load 00 01 < a >
k+24 Mul 00 01 < b >
k+26 Mul 10 01 8
k+28 Add 00 01 P1
k+30 St 00 01 < y >
k+32 Halt
k+34 a = 10
k+36 b = -4
k+38 330
k+40 y
k+42 P1
k+44 P2
В этой программе имеются 4 константы. Поскольку значения 1, 8, 28 меньше 255, то для их представления в машинных командах используется непосредственная адресация, в то время как для константы 330 отводится отдельная ячейка памяти.
Обозначения R1 – это номер регистра в блоке R, P1 и P2 в поле операнда – это адреса ячеек памяти, предназначенные для временного хранения промежуточных результатов.
Предположим, что в качестве пускового адреса программы задано значение 20, а в качестве исходной системы счисления – 16 с/с. Тогда вначале производится преобразование символьной записи программы в 2 с/с, а затем – в 16 с/с. Фактически такое представление программы – это содержимое входного текстового файла. В первой строке этого файла записывается в 10 с/с пусковой адрес программы, а затем машинные команды, переменные и константы в заданной системе счисления.
Для рассматриваемого примера будем иметь (слева всё, кроме пускового адреса, в 2 с/с, справа – в 16 с/с:
20 20
0001 00 01 00110110 1136
0100 10 01 00000001 4901
0010 00 01 00111110 213E
0001 00 01 00110110 1136
0101 00 01 00111010 513A
0010 00 01 01000000 2140
0001 00 01 00111000 1138
0101 00 01 00011100 511C
0011 00 01 01000000 3140
0110 00 01 00111110 613E
0010 00 01 00111110 213E
0001 00 01 00110110 1136
0101 00 01 00111000 5138
0101 10 01 00001000 5908
0011 00 01 00111110 313E
0010 01 01 00111100 253C
0000 00 00 00000000 0000
0000 00 00 00001010 000A значение а
1111 11 11 11111100 FFFC значение b
0000 00 01 01001010 014A значение 330
Результат работы программы (k+40) = 3310 = 2116 = 001000012 .
Необходимо обратить внимание, что адреса операндов в этой программе записаны вначале в двоичной, а затем в шестнадцатеричной системе счисления с учетом пускового адреса. Например,
<a> = (k+34)10 = 5410 = 3616 = 001101102
Поля памяти с адресами < y >, P1 и P2 в машинной записи программы не отображены, поскольку их содержимое не вводится извне, а формируется в программе.
Примечание. Если в качестве входной системы счисления задана 8 с/с, то машинное двоичное представление программы разделяется справа налево на триады, после чего каждая триада записывается одной восьмеричной цифрой. Если задана четверичная система счисления, то аналогично производится разделение двоичной записи программы справа налево, но по два разряда, значения которых затем изображаются четверичными цифрами 0, 1, 2 или 3.
Пример 2.
Результаты:
Блок-схема для примера 2 приведена на рис.2.
На рис.2 звездочкой () обозначены те переменные, адреса которых должны изменяться с шагом 2 в каждом цикле выполнения программы. В начальном состоянии этим переменным назначаются адреса переменных
. Шаг переадресации при индекс-адресации определяется содержимым регистра
, значение которого в конце каждого цикла увеличивается на 2.
Блоки на схеме рис.2 изображены как будто в линейном порядке, что визуально совпадает с внешним видом машинной программы, но на самом деле они выполняются не в таком порядке. Блок 3 передает управление на вычислительный блок 6, если , в противном случае выполняется следующий блок 4, который в свою очередь передает управление на вычислительный блок 7, если
. Если же
, то выполняется следующий вычислительный блок (блок 8). После каждого вычислительного блока управление передается на блок 8, который записывает полученный результат в ячейку
.
Программная реализация примера 2 в условных кодах имеет следующий вид:
k+0 Load 00 00 0 0 -> R0
2 St 00 00 < счетчик > 0 -> < счетчик >
4 Load 10 01 k+28 подготовка команды
6 St 00 01 k+100 перехода k+28
8 Load 10 01 k+32 подготовка команды
k+10 St 00 01 k+102 перехода k+32
12 Load 10 01 k+38 подготовка команды
14 St 00 01 k+104 перехода k+38
16 Load 10 01 k+42 подготовка команды
18 St 00 01 k+106 перехода k+42
k+20 Load 10 01 k+62 подготовка команды
22 St 00 01 k+108 перехода k+62
24 Load 11 10 < x*1 > xi -> R2
26 Cmp 10 10 6 (R2) <=> 6?
28 Jmp 10 10 k+100 УП при xi>= 6 на k+40
k+30 Cmp 00 10 < -6 > (R2) <=> -6?
32 Jmp 11 10 k+102 УП при xi<=-6 на k+44
34 Sub 00 01 < a > xi – a -> R2
36 Mul 00 01 < a > a(xi – a) -> R2
38 Jmp 11 11 k+104 БП на k+50
k+40 Add 10 10 1 xi + 1 -> R2
42 Jmp 11 11 k+106 БП на k+50
44 Mul 11 10 < x*i > xi * xi -> R2
46 Mul 11 10 < x*i > xi * xi2 -> R2
48 Add 00 10 < a > xi3 + a -> R2
k+50 St 11 10 < y*i > yi -> < yi >
52 Add 10 10 2 (R0) + 2 -> R0
54 Load 00 01 < счетчик > счетчик-> R1
56 Add 10 01 1 (R1) + 1 -> R1
58 St 00 01 < счетчик > R1 -> < счетчик >
k+60 Cmp 10 00 5 счетчик <=> 5?
62 Jmp 10 00 k+108 повторение цикла с k+24
64 Halt останов
66 a = 5
68 x1 = 3
k+70 x2 = -3
72 x3 = 10
74 x4 = -6
76 x5 = 20
78 y1
k+80 y2
82 y3
84 y4
86 y5
88 счетчик
Преобразование двоичной записи машинной программы в заданную систему счисления и формирование входного текстового файла выполняются таким же образом, как и в примере 1.
Пример 3.
В примерах 1 и 2 проверена работа команд Halt, Load, St, Add, Sub, Mul, Div, Cmp, Jump, а также все виды адресации. Остались непроверенными команды And, Or, Xor, Shl, Shr, Call и Ret. В связи с этим в примере 3 будет реализовано обращение к подпрограмме, в работе которой используются логические команды и команды сдвига.
В подпрограмме будет вычисляться следующее выражение:
В основной программе будет реализовано двукратное обращение к подпрограмме. При первом обращении на место формальных параметров a и b будут переданы значения переменных a1 и b1, при втором обращении – значения а2 и b2. Аналогично выходное значение y, получаемое в подпрограмме, будет присвоено соответственно переменным y1 и y2.
k+ 0 Load 00 01 < a1 >
2 St 00 01 < a >
4 Load 00 01 < b1 >
6 St 00 01 < b >
8 Call 00 03 k+30 { обращение к подпрограмме}
k+10 Load 00 01 < y .
12 St 00 01 < y1 >
k+14 Load 00 01 < AM >
16 St 00 01 < a >
18 Load 00 01 < b2 >
k+20 St 00 01 < b >
22 Call 00 03 k+30 { обращение к подпрограмме }
24 Load 00 01 < y .
26 St 00 01 < y2 >
28 Halt
k+30 Load 00 01 < a > { начало подпрограммы }
32 And 00 01 < b >
34 St 00 01 P1
36 Load 00 01 < a >
38 Shl 10 01 2
k+40 Or 00 01 P1
42 St 00 01 P1
44 Load 00 01 < b >
46 Shr 10 01 3
48 Xor 00 01 P1
k+50 St 00 01 < y >
52 Ret 00 03 00000000 { выход из подпрограммы }
54 a1 = 3355
56 b1 - 7767
58 а2 = 11435
k+60 b2 = 23876
62 y1
64 y2
66 a
68 b
k+70 y
72 P1
74 P2
Тестовые примеры фактически предназначены для для проверки корректности работы эмулятора. В связи с этим для каждого из них предварительно должны быть выполнены контрольные расчеты результатов, которые будут получены при безошибочной работе эмулятора.
Ниже приводится детальный поэтапный контрольный расчет для примера 3.
Первое обращение
a1 = 335510 = D1B16 ; b1 = 776710 = 1E5716
a1 and b1:
0000 1101 0001 1011
0001 1110 0101 0111
0000 1100 0001 0011
a1 shl 2:
0011 0100 0110 1100
b1 shr 3:
0000 0011 1100 1010
(a1 and b1) or (a1 shl 2):
0000 1100 0001 0011
0011 0100 0110 1100
0011 1100 0111 1111
y1 = (a1 and b1) or (a1 shl 2) xor (b1 shr 3):
0011 1100 0111 1111
0000 0011 1100 1010
0011 1111 1000 0101
y1 = 3FB516 = 1616310
Второе обращение
a2 = 1143510 = 2СAB16 ; b2 = 2054810 = 504416
a2 and b2:
0010 1100 1010 1011
0101 0000 0100 0100
0000 0000 0000 0000
a2 shl 2:
0000 0000 0000 0000
b2 shr 3:
0000 1010 0000 1000 100
(a2 and b2) or (a2 shl 2):
0000 0000 0000 0000
y2 = (a2 and b2) or (a2 shl 2) xor (b2 shr 3):
0000 0000 0000 0000
0000 1010 0000 1000
0000 1010 0000 1000
y2 = A0816 = 256810
3 РАЗРАБОТКА ЭМУЛЯТОРА
3.1 Программные модели узлов ЭВМ М1
Моделируемыми узлами ЭВМ М1 являются оперативная память, регистры и дешифратор. Им соответствуют в эмуляторе определенные переменные или массивы. Арифметико-логическое устройство АЛУ и сумматор адреса SA явным образом не моделируются: их функции выполняют соответствующие операторы программы.
Каждая ячейка памяти на машинном уровне – это последовательность битовых элементов. Две смежные ячейки памяти ЭВМ М1 могут интерпретироваться в машине как команда, если их содержимое направлено в регистр команд RK, или как обрабатываемые данные, если это содержимое поступает в блок регистров R или в операционные регистры OR1, OR2. В последнем случае данные могут обрабатываться как числа, если на дешифратор поступил код арифметической команды, или как последовательности бит, если дешифрирована логическая команда.
В общем случае память – это массив битовых строк. Однако язык Турбо Паскаль не имеет в явном виде средств для изображения и обработки таких строк. В связи с этим могут быть предложены два варианта моделирования памяти.
В первом варианте ячейка памяти рассматривается как строка типа string[8], каждый байт которой может принимать значение ‘0’ или ‘1’. Тогда модель памяти может быть представлена в следующем виде:
Type
string8 = string[8];
MemoryAr = array[0..255] of string8;
Var
Memory : MemoryAr;
Во втором варианте ячейка памяти изображается как множество из восьми элементов. Модель памяти для второго варианта:
Type
set8 = set of 1..8;
MemoryAr = array[0..255] of set8;
Var
Memory : MemoryAr;
Хотя внутренним представлением множества является битовая строка, но на уровне Паскаль-программы множество не может интерпретироваться как строка бит. Множество типа set8 лишь указывает, входят или не входят в его состав элементы 1 .. 8, что косвенным образом определяет значения 1 или 0 соответствующих разрядов ячейки памяти.
Регистры RS, OR1, OR2, Res, RP и блок регистров R моделируются аналогично памяти (string или set).
Регистр команд RK, включающий в себя поля KOP, MA, NR и AM, целесообразно моделировать записью, компонентами которой являются модели битовых строк.
Модель регистра RK для первого варианта:
Type
string2 = string[2];
string4 = string[4];
string8 = string[8];
RKType = record
KOP : string4;
MA,NR : string2;
AM : string8
end;
Var
RK : RKType;
Модель регистра RK для второго варианта:
Type
set2 = set of 1..2;
set4 = set of 1..4;
set8 = set of 1..8;
RKType = record
KOP : set4;
MA,NR : set2;
AM : set8
end;
Var
RK : RKType;
Рассмотрим теперь вопрос о модели дешифратора. В общем случае дешифратор – это устройство, имеющее входных и
выходных шин. Его функцией является активизация выходной шины, порядковый номер которой соответствует значению
-разрядного двоичного числа на входе дешифратора. Моделью дешифратора является оператор Case.
Предположим, что код операции RK.KOP (строка или множество) преобразован в программе в значение целочисленной переменной NumKOP типа byte. Тогда модель дешифратора может иметь вид:
Case NumKOP of
0 : ProcHalt;
1 : Load;
2 : Store;
3 : Add;
..................
15 : FromSubRoutine;
end;
Здесь ProcHalt, Load, Store, Add, …, FromSubRoutine – процедуры, реализующие в эмуляторе машинные операции останова, загрузки операнда, сохранения операнда, сложения, …, выхода из подпрограммы.
3.2 Ввод и преобразование исходных данных
Исходными данными для эмулятора является машинная программа ЭВМ М1 и обрабатываемая этой машиной информация.
Машинную программу целесообразно компоновать в том виде, как это представлено в п.2.3 для ЭВМ М1. В этом случае как машинные команды, так и обрабатываемые этими командами данные располагаются в непрерывной последовательности ячеек памяти и могут быть введены эмулятором из одного текстового файла.
В зависимости от технического задания исходные данные изображаются в четверичной, восьмеричной или шестнадцатеричной системе счисления. Если в задании определена 16 с/с, то входной текстовый файл может иметь, например, следующий вид:
20
1128
5919
4964
212С
112B
6904
312A
612C
212E
0000
FFE2
03EB
Здесь в первой строке текстового файла в 10 с/с записано значение пускового адреса машинной программы, в остальных строках – содержимое ячеек памяти в 16 с/с.
Процедура ввода, входящая в состав эмулятора, должна прочесть содержимое входного файла как последовательность строк, преобразовать их в двоичный эквивалент и заполнить значениями 0 и 1 соответствующие элементы модели памяти Memory. Пример программы ввода для эмулятора ЭВМ М1 (информация в текстовом файле записана в 2 с/с, модель памяти – массив множеств) приведен в прил.3.
В общем случае программа ввода может быть представлена следующим образом (в предположении, что содержимое одной строки текстового файла размещается в одной ячейке памяти):
Type
string80 = string[80];
Var
n, { кол-во строк машинной программы}
Adr, { адрес ячейки памяти }
SAK : byte; { счетчик адреса команд }
S : string80; { строка входного файла }
InFile : text; { входной текстовый файл }
Begin
Reset(InFile);
Readln(InFile,SAK);
n:=1; Adr:=SAK;
While not Eof(InFile) do
Begin
Inc(n);
Readln(InFile,S);
Удаление в строке S пробелов перед
первой цифрой
Преобразование строки S в двоичный
эквивалент и запись его в элемент
Memory с индексом Adr
Inc(Adr); { подготовка след.адреса }
End;
Close(InFile);
Преобразование строки S входного файла в битовую строку упрощается тем, что четверичная, восьмеричная и шестнадцатеричная системы счисления имеют основанием степень числа 2. Каждой цифре четверичной записи соответствуют две цифры двоичной записи, для восьмеричной записи – три цифры (триада), для шестнадцатеричной – четыре цифры (тетрада).
Предположим, что ячейка ЭВМ имеет длину 16 бит, а ее содержимое изображено в 16 с/с. Тогда соответствие между шестнадцатеричной и двоичной записями может быть представлено схемой, изображенной на рис.3.
Рис.3. Соответствие между двоичной и шестнадцатеричной записями
Следовательно, программа ввода должна переписать двоичное разложение первой шестнадцатеричной цифры строки S в первую тетраду двоичной записи, второй цифры – во вторую тетраду и т.д. Соответствие между шестнадцатеричной цифрой и ее двоичным эквивалентом в первом варианте модели памяти целесообразно представить в эмуляторе типизированной константой Ekv2.
Type
string4 = string[4];
Const
Ekv16 = ‘0123456789ABCDEF’;
Ekv2 : array[1..16] of string4 =
(‘0000’,’0001’,’0010’,’0011’,’0100’,’0101’,
’0110’,’0111’,’1000’,’1001’,’1010’,’1011’,
’1100’,’1101’,’1110’,’1111’);
После ввода очередной строки S в цикле просматривают символы этой строки, с помощью функции Pos определяют порядковый номер k очередного символа и его двоичный эквивалент из типизированного массива Ekv2:
Var
i,k : byte;
ch : char;
Begin
For i:=1 to 4 do
Begin
ch:=S[i]; k:=Pos(ch,Ekv16);
Перенос k-го элемента массива Ekv2
в i-ую тетраду ячейки Memory с
индексом Adr
End;
При использовании восьмеричной системы счисления для изображения 16-разрядной двоичной строки следует иметь в виду, что поскольку разделение двоичной последовательности на триады производится справа налево, то первая восьмеричная цифра может иметь только значение 0 или 1 и ей соответствует один, а не три бита двоичной строки.
При использовании второго варианта модели памяти преобразование входных строк в двоичный эквивалент имеет аналогичный характер.
3.3. Прямое и обратное преобразования строки или множества в число
Операнды, поступающие в АЛУ из ячеек памяти, представлены в виде строки или в виде множества. При выполнении над ними арифметических операций необходимо предварительно получить численные значения этих операндов, т.е выполнить преобразование строки в число или множества в число. После выполнения арифметической операции ее результат перед записью в память требуется преобразовать в двоичный эквивалент.
Преобразования множества в число и числа в множество представлены в эмуляторе ЭВМ М1 в виде процедур FromSetToNumber и FromNumberToSet (см. прил. 3).
Рассмотрим выполнение указанных преобразований для строк.
В строке S символами ‘0’ и ‘1’ изображается двоичное число. Будем считать это число положительным.
Пусть нам требуется преобразовать n-разрядную строку S, содержащую двоичные цифры числа, в численное значение целой переменной P. Учитывая, что первый разряд строки S – это знак числа, а значащими являются (n-1) цифр, эту строку можно представить в виде
,
где - символы строки S (‘0’ или ‘1’).
Такое представление строки фактически и является схемой ее преобразования в число. Вычисление записанного выше полинома следует проводить по схеме Горнера:
,
где - двоичные цифры 0 или 1 (цифра
, определяющая знак числа, равна нулю).
Преобразование числа Р в n-разрядную строку S выполняется в обратном порядке по схеме:
;
;
;
;
……………………………………….
;
;
Поскольку , то последний оператор эквивалентен следующему:
Отрицательные числа представлены в строке S в дополнительном коде. Следовательно, при переводе требуется в случае необходимости выполнить преобразование дополнительного кода в прямой код, а при преобразовании
– преобразование прямого кода в дополнительный.
Как известно, для получения дополнительного кода отрицательного двоичного числа требуется инверсировать его цифры, а затем добавить единицу к младшему разряду полученного числа. При переходе от дополнительного кода отрицательного числа к его прямому коду из дополнительного кода предварительно вычитается единица. Поскольку арифметические операции над строками невозможны, то эти действия должны производиться по отношению к численным представлениям содержимого преобразуемых строк. Возможные варианты преобразования и
представлены в приведенных ниже процедурах по отношению к 16-разрядным двоичным числам. При рассмотрении указанных процедур следует учитывать, что первый байт строки – это знак двоичного числа.
Procedure FromStringToNumber(Operand:string16;
Var Number:integer);
{ Преобразование строки в число }
Var i : byte;
Cond : boolean; { признак отрицательного числа }
Begin
Cond:=false;
If Operand[1]=’1’ then
Begin
Cond:=true;
For i:=1 to 16 do { инверсирование }
If Operand[i]=’1’ then { цифр отрицатель-}
Operand[i]:=’0’ { ного двоичного }
Else { числа }
Operand[i]:=’1’;
End;
Number:=0; { получение числен- }
For i:=2 to 16 do { ного значения по }
Begin { схеме Горнера }
Number:=2*Number;
If Operand[i]=’1’ then
Inc(Number);
End;
If Cond then { добавление 1, если }
Begin { был преобразован }
Inc(Number); { дополнительный код, }
Number:=-Number { и изменение знака }
End; { числа }
End { FromStringToNumber };
Procedure FromNumberToString(Number:integer;
Var Operand:string16);
{ Преобразование числа в строку }
Var i : byte;
k,
Divisor : integer; { делитель }
Cond : boolean; { признак отрицательного числа }
Begin
Cond:=false;
If Number<0 then
Begin { получение положитель- }
Cond:=true; { ного числа и вычи- }
Number:=-Number; { тание из него }
Dec(Number) { единицы }
End;
Divisor:=16384; { делитель = (n=16) }
Operand:=’0’;
For i:=2 to 16 do { последовательное }
Begin { формирование }
k:=Number div Divisor; { двоичных цифр }
If k>0 then { числа }
Operand:=Operand+’1’
Else
Operand:=Operand+’0’;
Number:=Number mod Divisor;
Divisor:=Divisor shr 1;
End;
If Cond then { инверсирование }
For i:=1 to 16 do { цифр отрицатель- }
If Operand[i]=’1’ then { ного числа (по- }
Operand[i]:=’0’ { лучение дополни- }
Else { тельного кода) }
Operand[i]:=’1’;
End { FromStringToNumber };
3.4. Индикация переполнения с фиксированной запятой
Арифметические операции, входящие в систему команд учебной ЭВМ, в эмуляторе выполняются в конечном счете над переменными типа integer или longint: integer, если длина разрядной сетки ЭВМ меньше или равна 16 двоичным разрядам, и longint в противном случае.
При выполнении Паскаль-программы в ПЭВМ переполнение целочисленных переменных не индицируется, т.е. такая ситуация не приводит к прерыванию программы, в том числе при включенной директиве компилятора R. Однако переменная в этом случае получает неправильное значение, что в дальнейшем неизбежно приводит к ошибкам в работе программы.
Рассмотрим следующий фрагмент программы:
{R+}
Var k,l,m,n,s : integer;
p,q : longint;
Begin
k:=25000; l:=25000;
m:=k+l; n:=k*l;
p:=k+l;
q:=k; q:=q+l;
Writeln(‘m = ‘,m,’ n = ‘,n);
Writeln(‘p = ‘,p,’ q = ‘.q);
s:=q;
Writeln(‘s = ‘,s);
В результате работы программы будет отпечатано:
m = -15536 n = -16832
P = -15536 q = 50000
S = -15536
В выражении k+l оба операнда имеют тип integer, поэтому и значение выражения будет иметь тот же тип. Это объясняет, в частности, почему переменная р типа longint получила неправильное значение.
В выражении q+l один из операндов имеет тип integer, второй – тип longint. Перед вычислением выражения значение переменной l преобразуется к старшему типу, т.е. типу longint. Вследствие этого переменной q присваивается правильное значение, равное 50000.
При вычислении выражений k+l и k*l директива R+ не индицирует выход значения выражения за пределы границ, допустимых для типа integer (-32768 .. 32767). Такая индикация (прерывание 201 Range check error) имеет место лишь при выполнении оператора s := q, когда производится попытка присвоить переменной s значение вне допустимого диапазона.
В системах команд учебных ЭВМ предусмотрено присваивание регистру признаков RP значения, индицирующего переполнение с фиксированной запятой (ПФЗ). Для того, чтобы эмулятор определил наличие ПФЗ, в процедурах выполнения арифметических операций следует предварительно преобразовать целочисленные операнды к типу real, выполнить соответствующую операцию и сравнить полученный результат с граничными значениями, соответствующими разрядной сетке ЭВМ.
Пусть учебная ЭВМ имеет разрядную сетку, состоящую из 26 двоичных разрядов. Поскольку один разряд при этом отводится для знака числа, то допустимый диапазон изменения целочисленной переменной в этом случае равен
Обозначим значение 33554431 как константу MaxNumber:
Const MaxNumber = 33554431;
Тогда индикация ПФЗ в процедуре сложения может быть выполнена следующим образом:
Var Operand1,Operand2,Res : longint;
a,b,c : real;
Begin
..............................
a:=Operand1; b:=Operand2;
c:=a+b;
If (c<-MaxNumber-1) or (c>MaxNumber) then ...
Аналогичные проверки производятся также в процедурах вычитания и умножения.
В процедуре деления ПФЗ не может иметь места, а для индикации деления на нуль должна производиться предварительная проверка целочисленного делителя на равенство его нулю.
3.5. Режимы работы эмулятора
Режимы работы эмулятора определяются естественными требованиями, которые возникают при отладке машинной программы.
Является очевидным, что машинную программу гораздо труднее понимать, чем Паскаль-программу; в машинной программе также значительно больше вероятность появления ошибок (неправильный адрес или значение операнда, случайная замена 0 на 1 в машинной команде и т.п.). Поэтому при отладке машинной программы необходимо анализировать состояние функциональных узлов ЭВМ (память и регистры) фактически после выполнения каждой машинной команды.
Приведенные выше соображения требуют включения в состав эмулятора по крайней мере следующих режимов его работы:
- ввод исходных данных (Input);
- полное выполнение программы (Run);
- шаговое выполнение программы (Step);
- просмотр содержимого памяти (Memory).
Выбор режима работы должен производиться путем передвижения строки-курсора по позициям основного меню.
В режиме Input производится ввод входного текстового файла, формирование значения пускового адреса и заполнение соответствующих ячеек оперативной памяти Memory.
Работа эмулятора может быть проверена путем решения нескольких тестовых примеров (для ЭВМ М1 в п.2.3 приведено три таких теста). Поэтому при проектировании режима Input необходимо предусмотреть ввод по запросу программы имени файла, содержащего требуемый тест.
В режиме Run выполняются все команды машинной программы вплоть до достижения команды останова.
В режиме Step после выполнения каждой очередной машинной команды на экране должно быть отображено состояние всех регистров машины. По отношению к ЭВМ М1 – это регистры SAK, RA, RS, RK, OR1, OR2, Res, RP, блок регистров R. Для регистров SAK и RA достаточно указать в рамке десятичные значения их содержимого, для RP представляется логичным вывести на экран его двоичное значение, для остальных регистров, кроме двоичного изображения их содержимого, целесообразно выводить на экран также соответствующее десятичное значение. Переход к выполнению следующей машинной команды должен осуществляться после нажатия клавиши Enter.
В режиме Memory должен быть организован на экране скроллинг содержимого оперативной памяти ЭВМ (адрес ячейки, двоичное и десятичное значение ее содержимого, а также значение в заданной по варианту системе счисления (4, 8 или 16 с/с). Для улучшения сервиса работы пользователя рекомендуется предусмотреть ввод по запросу эмулятора значений начального и конечного адресов просматриваемой области памяти.
Указанные режимы являются обязательными при проектировании эмулятора любой учебной ЭВМ. Кроме них в техническое задание могут быть включены также дополнительные режимы, например:
- печать заданной области памяти на принтере (PrintMem);
- печать на принтере в режиме Step содержимого регистров (PrintStep);
- перевод числа
,
(Trans10_16, Trans10_4, Trans10_8) ;
- перевод числа
,
(Trans16_10, Trans4_10, Trans8_10);
- контроль корректности исходных данных (Check) и др.
Остановимся несколько подробнее на режиме Check, в котором должна производиться синтаксическая проверка машинной программы.
Возможные синтаксические ошибки в машинной программе полностью определяются структурой ЭВМ и составом ее системы команд. В частности, по отношению к ЭВМ М1 должны быть выполнены следующие требования:
- в машинной программе обязательно должна быть команда останова;
- пусковой адрес должен быть четным;
- значение пускового адреса должно быть в диапазоне 0 .. 255;
- адреса операндов должны быть четными;
- для команды перехода допустима лишь косвенная адресация;
- для команд St, Call и Ret не должна использоваться непосредственная адресация и др.
При вводе строк исходного текстового файла должны быть предусмотрены проверки, связанные с представлением машинной программы в заданной системе счисления. Такими проверками могут быть:
- наличие на диске заданного текстового файла;
- размер входного файла (не является ли он пустым);
- длина строки текстового файла (количество цифр);
- отсутствие недопустимых символов в строке (например, символа “5” для 4 с/с или символа “K” для 16 с/с и т.п.).
При обнаружении синтаксических ошибок на экран должно выдаваться сообщение о месте и типе ошибки, после чего работа эмулятора должна прерываться.
3.6. Формирование меню
Наиболее простым способом организации меню является выбор позиции меню с помощью оператора Case.
Const
St : array[1..4] of string[32] =
(‘П о з и ц и я 1’,’П о з и ц и я 2’,
’П о з и ц и я 3’,’П о з и ц и я 4’);
Var i,Switch : byte;
Begin
ClrScr;
For i:=1 to 4 do
Begin
GotoXY(2*i+10,20);
Write(i:2,’ ‘,St[i]);
End;
GotoXY(20,3);
Write(‘Укажите номер позиции меню’);
Readln(Switch);
Case Switch of
1 : Proc1;
2 : Proc2;
3 : Proc3;
4 : Proc4;
end;
Позиции 1 .. 4 – это наименования режимов работы эмулятора;
Оператор Case формирует обращение к одной из процедур Proc1 .. Proc4 в зависимости от введенного значения переменной Switch.
Более совершенным является формирование такого меню, в котором строка-курсор указывает на выбираемую позицию путем ее цветового выделения. При этом клавиши перемещения курсора обеспечивают видимое передвижение по позициям меню, а активизация выбранной позиции производится нажатием заранее определенной управляющей клавиши (например, Enter или Tab). Пример формирования такого меню приведен в тексте эмулятора М1 (прил.3).
При формировании меню в эмуляторе ЭВМ М1 используются процедуры MakeMenu (модуль Emulat), MoveCursor, Frame и InsertColorString (модуль BasUnit).
В процедуре MakeMenu вначале формируется на экране рамка путем обращения к процедуре Frame.
Рамка, которую рисует на экране процедура Frame, может быть окаймлена одинарной (Line = 1), двойной (Line = 2) или широкой (Line = 3) линией. Цвет рамки и фоновый цвет ее внутренней области определяются значением формального параметра Color, которому в данном случае при обращении присваивается значение LightGrayBlack (фон – светлосерый, цвет символа – черный).
После отработки процедуры Frame на экране печатаются строки массива St, определяющие содержание позиций меню. Начальная активизация одной из позиций обеспечивается заданием значений переменным CurrRow (номер текущей строки экрана) и CurrCol (номер текущего столбца экрана), после чего процедура InsertColorString подсвечивает выделенную позицию цветом BlueYellow (фон – синий, символ – желтый).
Передвижение строки-курсора по позициям меню осуществляется процедурой MoveCursor, с помощью которой можно формировать как вертикальное, так и горизонтальное меню путем задания соответствующих значений ее формальным параметрам. Выход из процедуры MoveCursor производится при нажатии клавиши Enter или клавиши Tab. При этом текущее значение переменной CurrRow указывает положение активизированной позиции меню, после чего с помощью оператора Case осуществляется обращение к процедуре, соответствующей этой позиции.
3.7. Организация скроллинга
При вертикальном сдвиге текстового экрана вверх строки 2 .. 25 переписываются в строки 1 .. 24, а в строку 25 вводятся данные из оперативной памяти (содержимое строки типа string[80]). Сдвиг вниз осуществляется аналогично.
Вертикальный сдвиг экрана называют также скроллингом (scroll – рулон, свиток).
Пусть для заполнения строки экрана в программе используется переменная BufRow типа ScreenRowArray (см.описание типов в модуле DesUnit, прил.3).
Вертикальный сдвиг вверх:
Procedure ScreenToUp;
Var i : byte;
Begin
For i:=1 to 24 do
Screen[i]:=Screen[i+1];
Screen[25]:=BufRow;
End { ScreenToUp };
Вертикальный сдвиг вниз:
Procedure ScreenToDown;
Var i : byte;
Begin
For i:=25 downto 2 do
Screen[i]:=Screen[i-1];
Screen[1]:=BufRow;
End { ScreenToDown };
Аналогичным образом можно организовать скроллинг области экрана, ограниченной строками Row1, Row2 и столбцами Col1, Col2.
Для организации скроллинга можно использовать также процедуры InsLine и DelLine. Эти процедуры позволяют «прокручивать» часть текстового окна или весь экран вверх и вниз. InsLine вставляет пустую строку на место той, где находится в текущий момент курсор. Все нижние строки, начиная с нее, смещаются вниз на одну строку. Самая нижняя строка уйдет за нижнее поле окна и исчезнет.
Процедура DelLine удаляет строку, в которой находится курсор, подтягивая на ее место все нижестоящие строки. При этом освобождается самая нижняя строка экрана.
Все строки, которые освобождаются при работе процедур InsLine и DelLine, закрашиваются текущим цветом фона.
Пример программы, выполняющей скроллинг текста в окне экрана с помощью процедур InsLine и DelLine, приведен в прил.4.
3.8. Структура эмулятора
Программа эмулятора должна иметь модульную структуру (в противном случае общий объем ее объектного кода не может превышать 64 Кбайт).
Количество модулей и их иерархия в языке Турбо Паскаль не регламентируются. Требуется соблюдать лишь одно требование: в предложениях Uses не должно создаваться перекрестных связей между модулями (например, если в модуле RecUnit стоит фраза “Uses Crt, Dos, TabUnit, VenUnit”, а в модуле TabUnit – фраза “Uses Crt, RecUnit, LabUnit”).
При выполнении курсовой работы по проектированию эмулятора целесообразно воспользоваться приведенными ниже рекомендациями.
1) Имя модуля в соответствии с требованиями языка Турбо Паскаль должно совпадать с именем файла, который содержит данный модуль. В связи с этим длина имени модуля не может превышать 8 символов. Рекомендуется в качестве последних четырех символов в имени модуля использовать слово Unit (например, DesUnit, BasUnit и т.п.). Это позволяет легко отличать в каталоге файлы с именами модулей.
2) Все глобальные объявления констант, типов и переменных сосредоточить в одном модуле, присвоив ему имя DesUnit (от слова Description – описание).
3) Служебные (базовые) процедуры и функции, используемые в нескольких модулях, сосредоточить в модуле с именем BasUnit. Если при разработке эмулятора используется значительное количество процедур по управлению текстовым режимом экрана, то их целесообразно записать в отдельном модуле (например, с именем ScreUnit; screen – экран).
4) В основную программу ”Program Emulat” включить формирование заставки, формирование главного меню, установку в начальное состояние (очистку) узлов эмулируемой ЭВМ и выбор режима работы эмулятора.
5) В одном или в двух модулях разместить процедуры, реализующие режимы работы эмулятора (например, в модуле RegUnit; regime – режим).
6) В заставке указать название программы, учебную группу, фамилию и инициалы студента, название учебной ЭВМ. После вывода на экран заставки должна быть приостановлена работа программы до нажатия клавиши Enter.
7) Все процедуры, непосредственно эмулирующие машинные команды, рекомендуется разместить в отдельном модуле (например, в модуле InsUnit; instruction – команда).
В процессе отладки эмулятора рекомендуется перед именем каждого модуля установить директиву компилятора {$R+}, которая включает проверку корректности присваивания ординальным переменным новых значений, в том числе проверку выхода индекса за границу массива или строки.
Примером использования модулей в программной организации эмулятора является приведенный в приложении 3 эмулятор ЭВМ М1.
3.9. Стиль программирования
Основным принципом в проектировании программ является следующий: программа составляется в первую очередь для человека, а не для машины. Промышленные программные комплексы разрабатывает группа специалистов. В процессе его эксплуатации неизбежно возникает необходимость коррекции и модернизации комплекса, что будет требовать от разработчиков читать и изменять как собственные программы, так и программы их коллег. В связи с этим в текстах разрабатываемых программ обязательно должны содержаться средства, облегчающие понимание программы и улучшающие ее читабельность.
Специальных стандартов на тексты программ не существует. Тем не менее профессиональные программисты в написании текста программы придерживаются определенных правил, направленных на соблюдение приведенного выше принципа программирования.
При написании курсовой работы по проектированию эмулятора обязательным по отношению к тексту программы является выполнение указанных ниже требований.
1) Заголовки модулей и его секций, заголовки программы и каждого ее раздела начинать с первой позиции строки.
2) После заголовка модуля (Unit) указывать в комментарии основное назначение входящих в модуль процедур и функций.
3) После заголовка процедуры или функции приводить в комментарии краткое описание выполняемой ею работы.
4) Длинные тексты программы разделять комментариями на фрагменты. В комментарии кратко описывать работу фрагмента.
5) После объявления имени переменной в комментарии указывать назначение переменной.
6) Процедуры и функции отделять друг от друга пунктирной линией, заключенной в комментарий.
7) После слова End, завершающего блок процедуры или функции, в комментарии записывать имя данной процедуры или функции.
8) В качестве символов комментария использовать только фигурные скобки.
9) Каждое слово Begin начинать с новой строки.
10) Каждое слово End записывать с новой строки строго с той позиции, с которой начинается относящееся к нему слово Begin, Case или Record.
11) Текст программы между словами Begin и End, Case и End, Record и End должен быть сдвинут на один отступ вправо. Отступ равен двум позициям строки.
12) Текст программы после слов do, then, else должен начинаться с новой строки, сдвинутой на один отступ вправо.
13) Каждое слово Else должно быть расположено под тем словом If, к которому оно относится.
14) Разделы описаний, как правило, нужно располагать в том порядке, который предусмотрен стандартным языком Паскаль: Label, Const, Type, Var, описание процедур и функций.
15) Имена всех переменных, кроме параметров циклов и индексов элементов массивов и строк, записывать с прописной буквы. Это относится как к простым, так и к составным переменным (массивы, записи, множества и др.).
16) Имена переменных в программе в общем случае не должны отличаться от обозначений, принятых в исходной постановке задачи. Например, было бы недопустимым обозначить в программе эмулятора регистр команд именем Zz, даже снабдив его комментарием, что это имя является обозначение регистра команд.
17) Отдельные части имени, состоящего из нескольких слов, нужно выделять прописными буквами (например, FromSetToNumber).
18) Оператор Goto разрешается применять в программе в двух случаях:
а) для принудительного выхода из цикла;
б) для перехода к удаленному фрагменту программы.
Совершенно недопустимо использовать оператор Goto для перехода снизу вверх в программе.
Если оператор Goto производит переход на конец процедуры или функции, то его целесообразно заменить процедурой Exit. Таким же образом для принудительного выхода из цикла оператор Goto можно заменить процедурой Break, а для перехода на конец цикла – процедурой Continue.
19) Длина строки программы не должна превышать 68 символов (для печати текста программы редактором компилятора Паскаль на бумаге формата А4).
20) В программе вместо значений констант использовать, как правило, имена констант.
21) В разделе описания переменных использовать имена типов, а не описания типов. Например, вместо описания
Var
A : array[1..100] of string[40];
целесообразно использовать описание
Const Nmax = 100;
Type
string40 = string[40];
Ar = array[1..Nmax] of string40;
Var
A : Ar;
Последнее требование связано в основном с тем, что в большой программе любая переменная может быть использована где-либо как фактический параметр при обращениии к процедуре, а при таком обращении всегда требуется совпадение имени типа формального и фактического параметров.
3.10. Описание эмулятора ЭВМ М1
Эмулятор ЭВМ М1 включает в себя основную программу Emulat и четыре модуля: DesUnit, BasUnit, InsUnit и RegUnit.
Модуль DesUnit содержит глобальные описания констант, типов и переменных; модуль BasUnit – служебные процедуры и функции, используемые в других модулях; модуль InsUnit – процедуры реализации машинных команд; модуль RegUnit – процедуры реализации режимов работы эмулятора. Основная программа управляет работой эмулятора.
В начальном фрагменте основной программы производится очистка памяти Memory и регистров эмулируемой ЭВМ. После этого с помощью меню, формируемого процедурой MakeMenu, осуществляется выбор режима работы эмулятора (описание процедур формирования меню приведено в п.3.6).
В эмуляторе ЭВМ М1 предусмотрены следующие режимы работы:
- ввод исходных данных;
- полное выполнение машинной программы;
- шаговое выполнение программы;
- вывод области памяти;
- перевод числа
;
- выход из меню.
Ввод исходных данных осуществляется процедурой InputModul. Здесь из первой строки файла FileModul вводится значение пускового адреса SAK, после чего последовательно вводятся остальные строки файла вплоть до его исчерпания. Содержимое очередной строки файла FileModul, содержащее два машинных слова в двоичной системе счисления, вводится в строковую переменную S1, после чего из S1 выделяется значащая часть строки, запоминаемая в переменной S2. По содержимому строки S2 дважды формируется буферная переменная Slovo (множество типа set8) и записывается в элементы массива Memory с индексами k и k+1. Начальное значение индекса k равно значению SAK, после ввода каждой очередной строки файла значение переменной k увеличивается на 2.
Режим полного выполнения машинной программы реализуется процедурой Run. Здесь в цикле While последовательно просматриваются пары ячеек памяти Memory, начиная с адреса SAK. Управление циклом осуществляет булевская переменная CondExec, принимающая значение false при достижении команды останова (код операции 0000).
Каждая пара машинных слов заполняет поля KOP, MA, NR и AM регистра команд RK. Поля KOP и NR преобразуются в числовое значение переменных Switch и NumReg. После этого по значению переменной Switch с помощью оператора Case производится обращение к процедуре, которая реализует машинную команду, соответствующую данному коду операции KOP.
В модуле InsUnit, кроме процедур реализации машинных команд, имеются также процедуры формирования исполнительного адреса ExecAddress и формирования содержимого операционного регистра OR2. Указанные процедуры анализируют значение модификатора адреса NR, формируют исполнительный адрес операнда и его значение, записываемое в операционный регистр OR2. При работе процедур реализации машинных команд и формирования машинного адреса выполняется в случае необходимости преобразование множества в число и числа в множество, что осуществляется процедурами FromSetToNumber и FromNumberToSet.
В режиме шагового выполнения программы Step после исполнения каждой машинной команды на экране отображается содержимое регистров ЭВМ М1. Переход к выполнению следующей команды машинной программы производится после нажатия клавиши Enter.
В режиме “Вывод области памяти” осуществляется скроллинг области памяти Memory. Содержимое каждых двух ячеек памяти отображается в 2, 16 и 10 с/с.
В режиме перевода из десятичной в шестнадцатеричную системы счисления осуществляется указанный перевод для числа, задаваемого пользователем с клавиатуры ЭВМ.
Примечание. В прил.3 из методических соображений, а также для сокращения общего объема печатаемого текста приведены лишь основные фрагменты эмулятора ЭВМ М1.
3.11 Рекомендации по разработке эмулятора
Разработку большой программы всегда выполняют путем постепенного наращивания ее “скелета”, последовательно добавляя к ней новые процедуры и модули, расширяющие функции и улучшающие эстетическое оформление проектируемой программы. По отношению к программе-эмулятору рекомендуется выполнять ее разработку в следующей последовательности.
1. Сформировать файл исходных данных, разместив в нем наиболее простой тест (например, y = a + b).
2. Создать модули DesUnit, BasUnit, RegUnit и модуль основной программы Emulat.
В модуле DesUnit разместить описания констант, типов и переменных, обеспечивающих работу с оперативной памятью Memory и исходным текстовым файлом.
В модуль BasUnit включить процедуры WaitEnter, PrintString, PrintKeyAndWaitEnter (эти процедуры используются для приостановки решения при отладке программы) и функцию Space (используется при вводе и обработке входного файла).
В модуль RegUnit записать процедуру ввода исходных данных, их преобразования и занесения в ячейки памяти Memory.
Начальные функции основной программы Emulat:
- установка ячеек памяти Memory в нулевое состояние;
- обращение к процедуре ввода исходных данных.
Для отладки процедуры ввода исходных данных и проверки корректности заполнения памяти Memory можно использовать процедуру, аналогичную процедуре WriteMemory (модуль BasUnit эмулятора ЭВМ М1).
3. Добавить в модуль DesUnit объявления регистров ЭВМ, а в начальный фрагмент основной программы – операторы их обнуления. В модуль BasUnit включить процедуры прямого и обратного преобразования строки (множества) в число, проверить работу этих процедур.
4. Добавить в основную программу простое меню (см.п.3.5), заполнив в нем две позиции – “Ввод исходных данных” и “Полное выполнение программы”, в остальных позициях меню поставить пустые операторы.
5. Сформировать модуль InsUnit, включив в него процедуры реализации команд, необходимых для выполнения простого теста.
Разработать процедуру Run, осуществляющую перебор команд машинной программы и обращение к процедурам их реализации; включить эти процедуры в состав модуля RegUnit.
Віполнить отладку разработанных процедур. Для вывода на экран содержимого регистров ЭВМ можно использовать процедуру, аналогичную процедуре WriteRegisters (модуль BasUnit эмулятора ЭВМ М1).
Разработать и отладить процедуры реализации всех оставшихся машинных команд.
6. Заменить в основной программе простое меню более совершенным типом меню.
7. Разработать и отладить процедуры шагового выполнения программы (с отображением на экране содержимого регистров ЭВМ) и скроллинга содержимого памяти Memory.
8. Модернизировать процедуру ввода исходных данных, включив в нее формирование по запросу имени входного файла.
9. Разработать и отладить процедуру формирования заставки эмулятора.
10. Разработать и отладить процедуры реализации дополнительных режимов работы эмулятора.
11. Произвести тестирование работы эмулятора.
3.12 Содержание пояснительной записки
Пояснительная записка к курсовой работе должна содержать в себе следующие разделы.
1. Введение. Во введении следует определить понятие эмуляции, описать области применения эмуляторов, указать цель выполнения курсовой работы.
2. Описание учебной ЭВМ. Изобразить структурную схему ЭВМ, описать архитектуру машины, привести систему команд и описать последовательность их выполнения.
3. Программирование в кодах учебной ЭВМ. Привести используемые в дальнейшем мнемонические коды операций и других элементов машинной команды, для каждого теста написать программу в условных кодах (с комментариями), программу на машинном языке и содержимое исходного файла в заданной системе счисления, кратко описать программную реализацию тестов.
4. Описание эмулятора. Описать структуру, перечень и назначение программных модулей, режимы работы эмулятора, программную реализацию каждого режима, привести количественные характеристики эмулятора.
5. Заключение. В заключении должны быть кратко подведены итоги работы: что именно выполнено в курсовой работе, какой язык программирования и операционная система использованы при разработке программного продукта, основные характеристики программы (количество модулей, режимы работы, объем исходного и объектного кодов программы, результаты тестирования и пр.).
6. Список использованных источников.
7. Приложение 1. Текст эмулятора.
8. Приложение 2. Результаты тестирования.
1. Поляков Д.Б., Круглов И.Ю. Программирование в среде Турбо Паскаль. – М.: Изд-во МАИ, 1992. – 576 с.
2. Фаронов В.В. Турбо Паскаль 7.0. Начальный курс. – М.: “Нолидж”, 1997. – 616 с.
3. Фаронов В.В. Турбо Паскаль 7.0. Практика программирования. – М.: “Нолидж”, 1997. – 432 с.
4. Немнюгин С.А. Turbo Pascal. – СПб: “Питер”, 2001. – 496 с.
5. Немнюгин С.А. Turbo Pascal: практикум. – СПб: “Питер”, 2001. – 256 с.
6. Назаренко В.И. Основы программирования на языке Турбо Паскаль (на дискете). – Донецк, ДонНТУ, 2005. – 395 с.
7. Богумирский Б. Эффективная работа на IBM PC. – СПб: “Питер”, 1995. – 688 с.
Приложение 1
ТЕХНИЧЕСКОЕ ЗАДАНИЕ
НА КУРСОВОЕ ПРОЕКТИРОВАНИЕ
"УТВЕРЖДАЮ:"
Руководитель курсовой работы
______________________________
"____" ____________ 200 г.
Т Е Х Н И Ч Е С К О Е З А Д А Н И Е
Студенту_________________________гр.___________ факультета ВТИ
1. Срок защиты курсовой работы ____________________
2. Цель работы: выполнить проектирование, отладку и тестирование программы-эмулятора для эаданной учебной ЭВМ на языке Турбо Паскаль.
3. Вариант № _______
4. Тип вычислительной машины __________
5. Система счисления ______________
6. Программная модель узлов ЭВМ (string, set) _________________
7. Тестовые примеры:
Тест 1
Тест 2
Тест 3
8. Дополнительный режим эмулятора:
9. Содержание пояснительной записки:
Введение.
1. Учебная вычислительная машина _____ .
1.1. Архитектура ЭВМ.
1.2. Форматы представления информации и команд.
1.3. Система команд.
1.4. Принцип работы ЭВМ.
2. Программирование в кодах учебной ЭВМ.
2.1. Программирование формул.
2.2. Циклическая программа с разветвлениями.
2.3. Выполнение логических команд с использованием подпрограмм.
3. Программная модель учебной ЭВМ.
3.1. Назначение и область применения.
3.2. Технические характеристики программы.
3.3. Описание программы.
Заключение.
Приложение. Текст программы-эмулятора.
10. График выполнения курсовой работы.
1-я неделя: выдача задания, изучение системы команд, составление тестовых примеров в условных кодах и на машинном языке.
2-я неделя: разработка логической структуры эмулятора, выбор форматов представления входной, промежуточной и выходной информации, формирование входных текстовых файлов.
3-я неделя: разработка основной программы и процедур ввода исходных данных.
4-я неделя: отладка основной программы и процедур ввода исходных данных; разработка процедур выполнения машинных команд.
5-я неделя: отладка процедур выполнения машинных команд; разработка процедур реализации основных режимов работы эмулятора.
6-я неделя: отладка процедур реализации основных режимов работы эмулятора.
7-я неделя: разработка и отладка процедур реализации дополнительных режимов работы эмулятора.
8-я неделя: тестирование программы эмулятора.
9-я неделя: оформление пояснительной записки.
10-я неделя: представление курсовой работы на кафедру и ее защита.
Подпись студента _________________________
В А Р И А Н Т Ы З А Д А Н И Й
1. Т е с т 1
Содержанием теста 1 является вычисление арифметического выражения. Значение результата приводится в каждом варианте для контроля правильности решения, получаемого при эмуляции машинной программы.
Примечание 1. Если в системе команд учебной ЭВМ имеются команды арифметического сдвига, то их рекомендуется применять для замены умножения или деления на коэффициенты, являющиеся степенью числа 2. Применять с этой целью команды логического сдвига можно лишь по отношению к положительным операндам; для отрицательных операндов в этом случае будут получены неправильные результаты.
Примечание 2. В примерах теста 1 результатом деления является целая часть получаемого частного, что соответствует в Паскале операции div.
1.1. ;
1.2. ;
1.3. ;
1.4. ;
1.5. ;
1.6. ;
1.7. ;
1.8. ;
1.9. ;
1.10. ;
1.11. ;
1.12. ;
1.13. ;
1.14. ;
1.15. ;
2. Т е с т 2
Содержанием теста 2 является организация циклической программы с разветвлениями.
2.1.
2.2.
2.3.
2.4.
2.5.
2.6.
2.7.
2.8.
2.9.
2.10.
2.11.
2.12.
2.13.
2.14.
2.15.
3. Т е с т 3
В тесте 3 необходимо осуществить проверку выполнения логических команд, команд логического сдвига и способа организации подпрограмм с использованием соответствующих команд.
Учебные ЭВМ имеют различный набор логических команд и команд сдвига, а также различную длину слова памяти. В связи с этим не представляется возможным составить тесты, общие для всех учебных ЭВМ. Тест 3 студент составляет самостоятельно и согласовывает его с преподавателем при утверждении технического задания. Обязательным для теста 3 является выполнение следующих условий:
- в состав машинной программы входит одна подпрограмма;
- в машинной программе дважды производится обращение к подпрограмме, но для разных имен (адресов) входных и выходных переменных;
- подпрограмма реализует вычисление выражения, в состав которого входят все логические операции и операции логического сдвига, включенные в состав системы команд учебной ЭВМ.
Ниже приводится пример теста для ЭВМ B2, в систему команд которой входят команды логического умножения (and), логического сложения (or), суммы по модулю 2 (xor), отрицания (not) и сдвига логического вправо (shr). Слово памяти ЭВМ B2 имеет длину 32 бита.
y = (a shr 3) and b or (not c) xor (d shr 2);
z = (m shr 3) and n or (not p) xor (q shr 2);
a = AB08FC97; b = 12EC38F4; c = 563F2ECD;
d = 4950F036;
m = ABCDEF01; n = 12345678; p = FEA97643;
q = 345ABC01.
Результаты решения:
y = ABB4E5BF; z = 1C6122FC.
Рассмотрим порядок получения результата при вычислении значения y(a,b,c,d).
a = 1010 1011 0000 1000 1111 1100 1001 0111 = AB08FC97
a shr 3 = 0001 0000 0110 0000 0001 1000 1001 0000 = =10601890
c = 0101 0110 0011 1111 0010 1110 1100 1101 = 563F2ECD not c = 1010 1001 1100 0000 1101 0001 0011 0010 = =A9C0D132
d = 0100 1001 0101 0000 1111 0000 0011 0110 = 4950F036
d shr 2 = 0001 0010 0101 0100 0011 1100 0000 1101 = =12543C0D
(a shr 3) and b:
0001 0000 0110 0000 0001 1000 1001 0000 = 10601890
and
0001 0010 1110 1100 0011 1000 1111 0100 = 12EC38F4
¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾
0001 0000 0110 0000 0001 1000 1001 0000 = 10601890
(a shr 3) and b or (not c):
0001 0000 0110 0000 0001 1000 1001 0000 = 10601890
or
1010 1001 1100 0000 1101 0001 0011 0010 = A9C0D132
¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾
1011 1001 1110 0000 1101 1001 1011 0010 = B9E0D9B2
(a shr 3) and b or (not c) xor (d shr 2):
1011 1001 1110 0000 1101 1001 1011 0010 = B9E0D9B2
xor
0001 0010 0101 0100 0011 1100 0000 1101 = 12543C0D
¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾
1010 1011 1011 0100 1110 0101 1011 1111 = ABB4F5BF
4. ДОПОЛНИТЕЛЬНЫЕ РЕЖИМЫ ЭМУЛЯТОРА
Основными режимами работы эмулятора являются:
- ввод исходных данных;
- полное выполнение программы;
- шаговое выполнение программы;
- просмотр содержимого памяти.
Ниже приводятся дополнительные режимы, реализуемые в курсовой работе по заданию преподавателя. Во всех режимах должен осуществляться контроль корректности ввода с клавиатуры (отсутствие недопустимых символов, задание значения адреса в пределах емкости памяти Memory и т.п.). При обнаружении ошибки ввода программа должна выдать на экран сообщение об ошибке и предложить пользователю повторить ввод данных.
4.1. Поиск слова в памяти.
По запросу программы пользователь вводит с клавиатуры содержимое искомого слова в соответствующей системе счисления (4, 8 или 16 с/с) и адрес ячейки памяти, начиная с которой должен осуществляться поиск. Если такое слово найдено в памяти Memory, программа должна вывести на экран его адрес, в противном случае - сообщение об отсутствии этого слова.
4.2. Ввод слова в память.
По запросу программы пользователь вводит с клавиатуры адрес и значение слова памяти Memory. Программа должна выдать на экран старое значение слова и сделать запрос пользователю о подтверждении замены слова. Замена должна производиться при положительном ответе пользователя.
4.3. Выполнение фрагментов теста.
Режим является дополнительным по отношению к режиму пошагового выполнения программы и позволяет после каждого нажатия клавиши Enter выполнять группу команд вместо одной команды. На каждом этапе работы в данном режиме пользователь указывает по запросу программы адрес команды, до которой должен быть выполнен фрагмент машинной программы. При появлении в счетчике адреса команды SAK заданного адреса программа эмулятора должна выполнить текущую команду, выдать на экран содержимое регистров ЭВМ и повторить запрос о значении нового адреса останова.
4.4. Дешифрация команды программы.
По адресу памяти Memory, задаваемому пользователем, выполняется чтение команды и ее дешифрация на экране: код операции, модификатор адреса (если он имеется), маска (для команд перехода), адреса операндов, значения операндов и т.п.
4.5. Дешифрация заданной команды.
Аналогично п.4.4, но вместо адреса существующей команды пользователь набирает с клавиатуры содержимое произвольной команды.
4.6. Копирование блока памяти.
Пользователь указывает начальный Adr1 и конечный Adr2 адреса блока памяти Memory, а также адрес Adr3, начиная с которого нужно скопировать заданный блок (в ячейки Adr3, Adr3+1, Adr3+2, …).
При выполнении этого режима программа эмулятора вначале сдвигает вниз содержимое k = Adr2-Adr1+1 ячеек памяти, начиная с адреса Adr3, а затем записывает в освободившееся место копию содержимого ячеек с адресами от Adr1 до Adr2. При этом содержимое последних k ячеек памяти Memory выходит за ее нижнюю границу и исчезает.
4.7. Перемещение блока памяти.
Как и в режиме 4.6, пользователь задает значения адресов Adr1, Adr2 и Adr3. При этом фрагмент Adr1 .. Adr2 должен быть перенесен (не скопирован !) на участок, начиная с адреса Adr3. При этом предполагается, что Adr3 > Adr2.
В отличие от предыдущего режима, здесь количество информативных ячеек не изменяется.
Реализацию данного режима рекомендуется выполнять в следующем порядке.
- Содержимое ячеек Adr1 .. Adr2 (их количество равно d1 = Adr2 –Adr1+1) скопировать в буферный массив.
б) Содержимое ячеек Adr2+1 .. Adr3-1 (их количество равно d2 = Adr3 – Adr2 – 1) сдвинуть вверх в область с начальным адресом Adr1.
в) Содержимое буферного массива переписать в область памяти, начиная с адреса Adr1+d2.
4.8. Определение времени эмуляции.
С помощью процедуры GetTime программа должна определить время эмуляции теста. Для этого программа организует многократное выполнение теста (10 или более раз) и определяет среднее время работы по его эмуляции. Количество повторений теста задается пользователем с клавиатуры.
4.9. Контроль исходного теста.
Программа производит ввод машинной программы из входного файла и выполняет контроль корректности текста программы (наличие недопустимых символов, длина строки, значение пускового адреса и т.п.). При обнаружении ошибки должно быть выдано сообщение о типе и местонахождении этой ошибки.
4.10. Калькулятор в 10 с/c.
Пользователь набирает с клавиатуры два операнда; после нажатия операционной клавиши ("+", "–", "*" или "/") выполняется соответствующая операция, ее результат выдается на экран. Операнды и результат – целые числа, не превышающие по модулю значения константы MaxLongInt. При вводе с клавиатуры должен быть предусмотрен контроль корректности вводимых данных. Если результат превышает значение MaxLongInt, на экран должно быть выведено соответствующее сообщение.
4.11. Калькулятор в 4 c/c.
Аналогично п.4.10.
4.12. Калькулятор в 8 c/c.
Аналогично п.4.10.
4.13. Калькулятор в 16 c/c.
Аналогично п.4.10.
4.14. Перевод 10 4.
Пользователь набирает с клавиатуры произвольное целое число в десятичной системе счисления, на экран выдается его эквивалент в четверичной системе счисления. Отрицательные числа в 4 с/с должны быть представлены в дополнительном коде. Исходные числа по модулю не должны превышать значения константы MaxLongInt. При вводе с клавиатуры должен быть предусмотрен контроль корректности вводимых данных.
4.15. Перевод 10 8.
Аналогично п.4.14.
4.16. Перевод 10 16.
Аналогично п.4.14.
4.17. Перевод 4 10.
Пользователь набирает с клавиатуры произвольное целое число в четверичной системе счисления, на экран выдается его эквивалент в десятичной системе. Для отрицательных чисел используется дополнительный код. Исходные числа по модулю не должны превышать значения, соответствующего константе MaxLongInt. При вводе с клавиатуры должен быть предусмотрен контроль корректности вводимых данных.
4.18. Перевод 8 10.
Аналогично п.4.17.
4.19. Перевод 16 10.
Аналогично п.4.17.
4.20. Контроль исходного файла. По запросу программы пользователь указывает внешнее имя исходного текстового файла. Введенное с клавиатуры значение подвергается в программе синтаксическому контролю (наличие недопустимых символов, длина имени и его расширения и др.). При обнаружении ошибки выдается соответствующее сообщение с требованием повторить ввод имени файла. При выполнении процедуры Reset проверяется наличие ошибок открытия файла (отсутствие файла на диске, неправильный путь к файлу, неготовность дисковода и др.), при обнаружении ошибки на экран выдается сообщение об ошибке. При нормальном открытии файла программа определяет степень его заполнения (количество строк в текстовом файле и его размер в байтах).
Указание. Для определения размера текстового файла использовать процедуру FindFirst, входящую в стандартный модуль Dos.
4.21. Формирование строки статуса. Строка статуса определяет назначение управляющих клавишей при работе программы. Для ее размещения обычно отводят последнюю, двадцать пятую, строку экрана. Информация, размещаемая в строке статуса, зависит от режима работы программы. Во всех режимах в левой части строки статуса должна быть надпись "F10 - выход", определяющая выход из программы. В пошаговом режиме и в режиме просмотра памяти дополнительно разместить надпись "Esc - рестарт", которая должна означать возврат в меню, позицией которого является данный режим. В пошаговом режиме должна быть также надпись "Enter - работа", определяющая переход к выполнению следующей команды машинной программы. В режиме просмотра памяти в правой части строки статуса разместить надпись "Down,PgDn - просмотр", если в окне просмотра находится начальная часть массива Memory, "Up,PgUp - просмотр", если в этом окне находится конечная часть массива Memory, "Up, PgUp/Down,PgDn - просмотр" - для промежуточной части данного массива. Реакция на нажатие управляющих клавишей осуществляется в программе при приостановке ее работы (выполнение очередной команды в пошаговом режиме, "перелистывание" содержимого памяти в режиме просмотра и т.п.).
4.22. Формирование меню исходных файлов. При старте эмулятора должен быть автоматически выполнен просмотр текущего каталога и сформирован список имен файлов, имеющих расширение dat. Выдача меню исходных файлов на экран должна производиться в режиме "Ввод исходного файла". При этом необходимо обеспечить передвижение строки-курсора по меню и чтение файла, соответствующего выбранной позиции меню.
4.23. Формирование справочной информации. В данном случае речь идет о реализации Help-режима, определяющего выдачу справочной информации при нажатии клавиши F1. Такая информация должна выдаваться в следующих случаях:
- информация общего характера (назначение программы, перечень и краткая характеристика режимов работы и т.п.) - при выдаче на экран заставки программы;
- подробная информация о том режиме или подрежиме работы, позицию которого в меню указывает строка-курсор.
Для выдачи справочной информации целесообразно создавать окно просмотра, занимающее часть экрана на фоне других визуальных изображений (например, окно 12 х 46). Для размещения справочной информации используется типизированный файл с компонентами типа string[n], где n определяется длиной строки окна просмотра. В программе в зависимости от положения строки-курсора из типизированного файла выбирается та часть, которая относится к данному режиму (в программе указываются номера начального и конечного компонентов файла для каждого режима). Исходная справочная информация размещается в текстовом файле, строки которого по длине не превышают значения n, после чего отдельная сервисная программа читает текстовый файл и формирует соответствующий типизированный файл.
4.24. Создание и просмотр файла трассировки. Рассматривается как подрежим, используемый в режиме полного выполнения программы. При его включении после выполнения каждой команды машинной программы в файл заносится информация о состоянии регистров машины. Просмотр файла организуется в эмуляторе аналогично режиму просмотра содержимого памяти Memory. Файл трассировки целесообразно создавать в виде типизированного файла, компонентами которого являются записи.
4.25. Статистика обращения к памяти. Рассматривается как подрежим, используемый в режимах Run и Step. После окончания решения задачи на экран должна быть выведена следующая информация:
- общее количество операций чтения из памяти Memory;
- адреса ячеек чтения с указанием количества обращений к этим ячейкам; адреса должны быть сгруппированы в порядке убывания количеств обращений к соответствующим ячейкам;
- аналогично для операций записи в память.
4.26. Статистика выполнения команд машинной программы. Рассматривается как подрежим, используемый в режимах Run и Step. После окончания решения задачи на экран должна быть выведена информация о количествах выполнения каждой машинной команды учебной ЭВМ с группировкой по убыванию этих количеств.
4.27. Используя процедуры GetDate и GetTime, вывести на экран информацию о текущей дате и текущем времени в приведенном ниже виде.
Первая строка: “15 января 2005 года”.
Вторая строка: “10 часов 15 минут 32 секунды”.
При этом в зависимости от значений числительных нужно формировать соответствующий падеж существительного. Например, 10 минут, но 21 минута, 22 минуты.
4.28. Калькулятор длинных чисел в 4 с/с.
В отличие от п.4.11, исходные числа и результат могут значительно превосходить значение константы MaxLongInt (например, иметь 40 разрядов, в то время как MaxLongInt в 4 с/с имеет 16 разрядов). По согласованию с преподавателем здесь можно ограничиться операциями сложения и вычитания).
4.29. Калькулятор длинных чисел в 8 с/с.
В отличие от п.4.12, исходные числа и результат могут значительно превосходить значение константы MaxLongInt (например, иметь 40 разрядов, в то время как MaxLongInt в 8 с/с имеет 11 разрядов). По согласованию с преподавателем здесь можно ограничиться операциями сложения и вычитания).
4.30. Калькулятор длинных чисел в 10 с/с.
В отличие от п.4.13, исходные числа и результат могут значительно превосходить значение константы MaxLongInt (например, иметь 40 разрядов, в то время как MaxLongInt в 10 с/с имеет 10 разрядов). По согласованию с преподавателем здесь можно ограничиться операциями сложения и вычитания).
31. Калькулятор тригонометрических функций sin(x) и cos(x). При этом аргумент должен быть задан в градусах и минутах в диапазоне от до
.
Указания.
- Заданное значение аргумента привести к диапазону 0 .. 2
радиан.
- Вычисление функций производить по их разложению в ряд Маклорена.
3) Значения функций печатать в виде целой и дробной частей, разделенных точкой. Для дробной части отвести 6 позиций.
Приложение 3
ТЕКСТ ЭМУЛЯТОРА ЭВМ М1
UNIT DesUnit;
{ Глобальные описания констант,типов и переменных }
Interface
Uses Crt;
Const
MaxAddress = 255; { максимальный адрес оперативной памяти }
MaxNumber = 32767.0; { макс.значение 16-разрядного двоично-}
{ го числа для индикации переполнения }
Enter = 13; { код клавиши Enter }
PressKey = 'Нажмите клавишу "В В О Д" ';
BlackLightGray = 16*Black+LightGray; { константы цветовых }
BlackLightGreen = 16*Black+LightGreen; { атрибутов }
BlueLightGray = 16*Blue+LightGray;
BlueLightRed = 16*Blue+LightRed;
BlueYellow = 16*Blue+Yellow;
BlueWhite = 16*Blue+White;
GreenBlack = 16*Green+Black+128;
CyanBlack = 16*Cyan+Black;
RedWhite = 16*Red+White;
MagentaLightGreen = 16*Magenta+LightGreen;
MagentaLightCyan = 16*Magenta+LightCyan;
MagentaYellow = 16*Magenta+Yellow;
MagentaWhite = 16*Magenta+White;
BrownBlack = 16*Brown+Black;
BrownDarkGray = 16*Brown+DarkGray;
BrownLightCyan = 16*Brown+LightCyan;
BrownWhite = 16*Brown+White;
LightGrayBlack = 16*LightGray+Black
Type
set2 = set of 1..2; { описания типов }
set4 = set of 1..4; { для памяти и регистров }
set8 = set of 1..8;
set16 = set of 1..16;
RKType = record { тип регистра команд }
KOP : set4; { код операции }
MA, { модификатор адреса
NR : set2; { номер регистра }
AM : set8; { адрес операнда }
end;
RegType = array[0..3] of set16; { тип блока регистров }
MemoryType = array[0..MaxAddress] of set8;{ тип памяти }
String2 = string[2];
String4 = string[4];
String8 = string[8];
String12 = string[12];
String16 = string[16];
String80 = string[80];
ScreenColRange = 1..80; { тип номера столбца экрана }
ScreenRowRange = 1..25; { тип номера строки экрана }
ScreenChar = record { тип элемента видеопамяти }
Symbol : char; { символ }
Attrib : byte; { атрибут }
end;
ScreenRowArray = array[ScreenColrange] of ScreenChar;
{ тип строки экрана }
ScreenArray = array[ScreenRowRange] of ScreenRowArray;
{ тип видеопамяти }
ScreenPointer = ^ScreenArray;
Const { информационные сообщения }
Mes0 : string[78] = ' Esc \ выход '+
' \Enter \ выполнить';
Mes1 : string[78] = 'Esc \ выход \'+Chr(24)+Chr(25)+
' \ указатель'+' \Enter \ выполнить';
MesReg : string[78] = ' Работа режима закончена. '+
'Нажмите клавишу Enter ';
MesStep : string[78] = 'Esc \ выход '+
' \Enter \ выполнение команды';
Var
Aisp, { исполнительный адрес }
SAK, { счетчик адреса команд }
SAK1, { буферная переменная }
RA, { регистр адреса }
NumReg { номер регистра в блоке R }
: byte;
RP : set4; { регистр признаков }
Slovo, { буферная переменная }
RS : set8; { регистр слова }
RK : RKType; { регистр команд }
OR1, OR2, { операционные регистры }
Res : set16; { регистр результата }
Number1, Number2 { численные значения операндов }
: integer;
CondExec : boolean; { признак выполнения маш.программы }
Reg : RegType; { блок регистров }
Memory : MemoryType; { оперативная память }
CurrRow,CurrCol, { текущие строка и колонка экрана }
Key: byte; { ординальный номер символа }
RowAr : ScreenRowArray; { строка видеопамяти }
Screen : ScreenArray
absolute $B800:$0000; { видеопамять }
BufScreen1,BufScreen2, { указатели буферных массивов }
BufScreen3 { для сохранения видеопамяти }
: ScreenPointer;
FileModul { файл исходных данных }
: text;
Implementation
End.
{$R+}
UNIT BasUnit;
{ Базовые процедуры и функции }
Interface
Uses Crt,Dos,DesUnit,Printer;
Procedure WaitEnter;
Function Space(S:String80; k:byte):byte;
Function NotSpace(S:String80; k:byte):byte;
Function GetKey : byte;
Procedure SetCursor(Cursor:word);
Procedure MoveFromScreen(Var BufScreen:ScreenPointer;
Row1,Row2:byte);
Procedure MoveToScreen(Var BufScreen:ScreenPointer;
Row1,Row2:byte);
Procedure InsertColorString(Row,Col,Len,Color : byte);
Procedure ClrEolXY(Col:ScreenColRange; Row:ScreenRowRange;
Color:byte);
Procedure ClearScreen(x1,y1,x2,y2,Attrib:word);
Procedure WriteColor(S:string; Color:byte);
Procedure WriteXY(S:string; Col:ScreenColRange;
Row:ScreenRowRange; Color:byte);
Function FillString(Len:byte; ch:char) : string;
Function CenterString(S:string; Len:byte):string;
Procedure PrintError(Error:string);
Function GetNumber(Prompt:string; Low,High:real; m,n:byte;
Var Result:boolean) : real;
Procedure Frame(x1,y1,x2,y2,Line,Color : byte);
Procedure MoveCursor(UpRow,DownRow,StepRow,LeftCol,RightCol,
StepCol,Len,NewColor,OldColor:byte);
Procedure WriteLastString(S:string; BlueYellow,
LightGrayBlack:byte);
Procedure FromSetToNumber(Operand:set16;
Var Number:integer);
Procedure FromNumberToSet(Number:integer;
Var Operand:set16);
Procedure StartPosMemory;
Procedure StartPosRegisters;
Procedure WriteMemory(Adr1,Adr2:byte);
Procedure WriteRegisters;
Implementation
{ ------------------------------------------------------- }
Procedure WaitEnter;
{ Задержка выполнения программы до тех пор, }
{ пока не будет нажата клавиша Enter )
Var ch : char;
Begin
Repeat
ch:=ReadKey;
Until ord(ch) = Enter;
End { WaitEnter };
{ ------------------------------------------------------- }
Function Space(S:String80; k:byte):byte;
{ Поиск ближайшего пробела, начиная с позиции k }
{ строки S; Space = 0, если пробел не обнаружен }
Var i : byte;
Begin
Space:=0;
For i:=k to length(S) do
If S[i]=’ ‘ then
Begin
Space:=i; Exit
End;
End { Space };
{ ------------------------------------------------------- }
Function NotSpace(S:String80; k:byte):byte;
{ Поиск ближайшего непробела, начиная с позиции k }
{ строки S; NotSpace=0, если непробел не обнаружен }
Var i : byte;
Begin
NotSpace:=0;
For i:=k to length(S) do
If S[i]<>’ ‘ then
Begin
NotSpace:=i; Exit
End;
End { NotSpace };
{ ------------------------------------------------------- }
Function GetKey : byte;
[ Чтение символа клавиатуры }
Var ch : char;
Begin
ch := ReadKey;
If ord(ch) = 0 then { Расширенный код символа }
GetKey := ord(ReadKey)
Else
GetKey := ord(ch); { Нормальный код символа }
End { GetKey };
{ ------------------------------------------------------- }
Procedure SetCursor(Cursor:word);
{ Установка формы курсора (Cursor = $2000 - отключение }
{ курсора, Cursor = $0607 - нормальный курсор) }
Var Reg : Registers;
Begin
With Reg do
Begin
AH:=1; BH:=0;
CH:=Hi(Cursor); CL:=Lo(Cursor);
End;
Intr($10,Reg);
End { SetCursor };
{ ------------------------------------------------------- }
Procedure MoveFromScreen(Var BufScreen:ScreenPointer;
Row1,Row2:byte);
{ Сохранение области экрана, ограниченной строками Row1,Row2 }
Var Len : word;
Begin
New(BufScreen);
Len:=(Row2-Row1+1)*80 shl 1;
Move(Screen[Row1],BufScreen^,Len);
End { MoveFromScreen };
{ ------------------------------------------------------- }
Procedure MoveToScreen(Var BufScreen:ScreenPointer;
Row1,Row2:byte);
{ Восстановление области экрана, ограниченной }
{ строками Row1,Row2 }
Var Len : word;
Begin
Len:=(Row2-Row1+1)*80 shl 1;
Move(BufScreen^,Screen[Row1],Len);
Dispose(BufScreen);
End { MoveFromScreen };
{ ------------------------------------------------------- }
Procedure InsertColorString(Row,Col,Len,Color : byte);
{ Выделение строки Row длиной Len цветом Color с позиции Col }
{ (изменение атрибутов знакомест строки экрана) }
Var j : byte;
Begin
For j:=Col to Col+Len do
Screen[Row,j].Attrib := Color;
End { InstColorString };
{ ------------------------------------------------------- }
Procedure ClrEolXY(Col:ScreenColRange; Row:ScreenRowRange;
Color:byte);
{ Очистка до конца текущей строки экрана в заданном }
{ цвете (начиная с позиции Col, Row }
Begin
GotoXY(Col, Row);
TextAttr := Color; ClrEOL;
End { ClrEolXY };
{ ------------------------------------------------------- }
Procedure ClearScreen(x1,y1,x2,y2,Attrib:word);
{ Очистка области экрана, ограниченной координатами }
{ x1,y1 ( левый верхний угол) и x2,y2 (правый нижний }
{ угол) фоновым цветом Attrib }
Var Reg : Registers;
Begin
If (x1>x2) or (y1>y2) then { Неправильные значения }
Exit;
With Reg do
Begin
AX := $0600; { Очистка экрана с помощью BIOS }
BH := Attrib;
CH := Pred(y1); CL := Pred(x1);
DH := Pred(y2); DL := Pred(x2);
Intr($10, Reg);
End;
End { ClearScreen };
{ ------------------------------------------------------- }
Procedure WriteColor(S : string; Color : byte);
{ Вывод строки на экран в заданном цвете }
Begin
TextAttr:=Color; Write(S);
End { WriteColor ;}
{ ------------------------------------------------------- }
Procedure WriteXY(S:string; Col:ScreenColRange;
Row:ScreenRowRange; Color:byte);
{ Вывод строки S на экран с позиции Col,Row в цвете Color }
Begin
GotoXY(Col,Row);
TextAttr:=Color; Write(S);
End { WriteXY };
{ ------------------------------------------------------- }
Function FillString(Len:byte; ch:char) : string;
{ Заполнение Len байт строки символом ch }
Var S : string;
Begin
S[0]:=chr(Len);
FillChar(S[1],Len,ch); FillString := S;
End { FillString };
{ ------------------------------------------------------- }
Function CenterString(S:string; Len:byte):string;
{ Установка строки S по центру поля длиной Len байт }
Var l : byte;
S1 : string;
Begin
l:=length(S) div 2; S1:=FillString(Len,' ');
Insert(S,S1,(Len div 2)-l+1);
CenterString:=S1;
End { CenterString };
{ ------------------------------------------------------- }
Procedure PrintError(Error : string);
{ Вывод на экран в строку 25 сообщения об ошибке }
Var S : string;
Begin
MoveFromScreen(BufScreen3,25,25);
ClrEOLXY(1,25,RedWhite);
S:=' '+Error + '. Нажмите Escape'; Write(S);
Sound(220); Delay(300); NoSound;
Repeat
Key := GetKey;
Until Key = 27;
MoveToScreen(BufScreen3,25,25);
End { PrintError };
{ ------------------------------------------------------- }
Function GetNumber(Prompt:string; Low,High:real; m,n:byte;
Var Result:boolean) : real;
{ Ввод с клавиатуры вещественного числа. Строка Prompt – за- }
{ прос ввода; Low,Hight - допустимый диапазон вводимого числа;}
{ m,n - формат печати числа в сообщении об ошибке ввода; }
{ Result - признак результата. При ошибке ввода (неправильный }
{ формат или неправильное значение) в строке 25 печатается со-}
{ общение "Число должно быть в пределах от Low до Hight”. Со- }
{ общение сопровождается звук. сигналом. Запрос ввода повто- }
{ ряется до тех пор, пока не будет введено правильное значение}
Var S,S1,S2 : string[12];
Error : integer;
L : real;
Begin
TextAttr:=MagentaWhite;
Repeat
WriteXY(Prompt + ': ',1,24,MagentaWhite);
ClrEol; SetCursor(7);
Readln(S); SetCursor($2000);
Val(S,L,Error);
Result := (Error = 0) and (L >= Low) and (L <= High);
Str(Low:n:n, S1); Str(High:n:n, S2);
If not Result then
PrintError('Число должно быть в пределах от ' + S1 +
' до ' + S2)
Until Result;
ClrEolXY(1,24,MagentaWhite); GetNumber := L;
End { GetNumber };
{ ------------------------------------------------------- }
Procedure Frame(x1,y1,x2,y2,Line,Color:byte);
{ Вычерчивание прямоугольной рамки с заданными координатами }
{ одинарной (Line=1), двойной (Line=2) или широкой (Line=3) }
{ линией; цвет и фон - Color; при Line=0 рамка не вычерчи- }
{ вается, но прямоугольная область закрашивается фоновым }
{ цветом Color }
Var ChGr, { символ горизонтальной линии рамки }
ChVr, { символ вертикальной линии рамки }
LfUp, { левый верхний угол рамки }
LfDw, { левый нижний угол }
RtUp, { правый верхний угол }
RtDw : char; { правый нижний угол }
i : byte;
Begin
Case Line of
0 : Begin
ChGr:=#32; ChVr:=#32; LfUp:=#32;
LfDw:=#32; RtUp:=#32; RtDw:=#32
End;
1 : Begin
ChGr:=#196; ChVr:=#179; LfUp:=#218;
LfDw:=#192; RtUp:=#191; RtDw:=#217
End;
2 : Begin
ChGr:=#205; ChVr:=#186; LfUp:=#201;
LfDw:=#200; RtUp:=#187; RtDw:=#188
End;
3 : Begin
ChGr:=#176; ChVr:=#176; LfUp:=#176;
LfDw:=#176; RtUp:=#176; RtDw:=#176
End;
End;
WriteXY(LfUp,x1,y1,Color); Write(FillString(x2-x1,ChGr));
WriteXY(RtUp,x2,y1,Color);
For i:=Succ(y1) to Pred(y2) do
Begin
WriteXY(ChVr,x1,i,Color); WriteXY(ChVr,x2,i,Color)
End;
WriteXY(LfDw,x1,y2,Color); Write(FillString(x2-x1,ChGr));
WriteXY(RtDw,x2,y2,Color);
ClearScreen(x1+1,y1+1,x2-1,y2-1,Color);
End { Frame };
{ ------------------------------------------------------- }
Procedure MoveCursor(UpRow,DownRow,StepRow,LeftCol,RightCol,
StepCol,Len,NewColor,OldColor:byte);
{ Перемещение курсора-строки по экрану: }
{ UpRow,DownRow - верхняя и нижняя границы перемещения }
{ курсора; }
{ StepRow - шаг перемещения курсора по вертикали; }
{ LeftCol, RightCol - левая и нижняя границы перемещения; }
{ StepCol - шаг перемещения по горизонтали; }
{ Len - длина строки-курсора; }
{ NewColor, OldColor - новые и старые цветовые атрибуты }
{ строки-курсора и остального поля }
Var Cond : boolean;
Begin
Repeat
Key:=GetKey; Cond:=false;
Case Key of
13,27 : Cond := true; Enter,Escape
72 : If CurrRow >= UpRow+StepRow then { Up }
Begin
InsertColorString(CurrRow,CurrCol,Len,
OldColor);
CurrRow := CurrRow-StepRow;
End;
80 : If CurrRow <= DownRow-StepRow then { Down }
Begin
InsertColorString(CurrRow,CurrCol,Len,
OldColor);
CurrRow := CurrRow+StepRow;
End;
77 : If CurrCol+StepCol <= RightCol then { Right }
Begin
InsertColorString(CurrRow,CurrCol,Len,
OldColor);
CurrCol := CurrCol+StepCol;
End;
75 : If CurrCol-StepCol >= LeftCol then { Left }
Begin
InsertColorString(CurrRow,CurrCol,Len,
OldColor);
CurrCol := CurrCol-StepCol;
End;
end;
InsertColorString(CurrRow,CurrCol,Len,OldColor);
Until Cond;
End { MoveCursor };
{ ------------------------------------------------------- }
Procedure WriteLastString(S:string; BlueYellow,
LightGrayBlack:byte);
{ Печать сообщения в последней строке экрана. Текст сообщения }
{ разделен на фрагменты символами "\". Нечетные фрагменты вы- }
{ деляются цветом BlueYellow, четные - цветом LightGrayBlack }
Var k : byte;
St : string80;
Begin
ClrEolXY(1,25,LightGrayBlack);
S:=S+'\'; GotoXY(1,25);
While S<>'' do
Begin
WriteColor(‘.’,LightGrayBlack);
k:=Pos(‘\’,S);
WriteColor(Copy(S,1,pred(k),BlueYellow);
Delete(S,1,k);
k:=Pos(‘\’,S);
If S[1]=’\’ then
St:=’-‘
Else
St:=’-‘+Copy(S,1,pred(k));
WriteColor(St,LightGrayBlack);
Delete(S,1,k);
End;
End WriteLastString ;
{ ------------------------------------------------------- }
Procedure FromSetToNumber(Operand:set16; Var Number:integer);
{ Преобразование множества в число }
Var i : byte;
Cond : boolean;
Begin
Cond:=false;
If 1 in Operand then
Begin
Cond:=true;
For i:=1 to 16 do { инверсирование цифр }
If i in Operand then { отрицательного двоичного }
Operand:=Operand-[i] { числа }
Else
Operand:=Operand+[i];
End;
Number:=0;
For i:=2 to 16 do { получение численного }
Begin { значения по схеме }
Number:=2*Number; { Горнера }
If i in Operand then
Number:=Number+1;
End;
If Cond then { добавление единицы, если }
Begin { был преобразован дополни-}
Inc(Number); { тельный код, и изменение }
Number:=-Number { знака числа }
End;
End { FromSetToNumber };
{ ------------------------------------------------------- }
Procedure FromNumberToSet(Number:integer; Var Operand:set16);
{ Преобразование числа в множество }
Var i : byte;
k,Divisor : integer;
Cond : boolean;
Begin
Cond:=false;
If Number<0 then
Begin
Cond:=true; { получение положительного }
Number:=-Number; { числа и вычитание из него }
Dec(Number); { единицы }
End;
Divisor:=16384; Operand:=[]; { делитель равен 2**14 }
For i:=2 to 16 do
Begin { последовательное формиро- }
k:=Number div Divisor; { вание двоичных цифр числа
If k>0 then
Operand:=Operand + [i];
Number:=Number mod Divisor;
Divisor:=Divisor shr 1;
End;
If Cond then
For i:=1 to 16 do { инверсирование цифр отри- }
If i in Operand then { цательного числа (получе- }
Operand:=Operand - [i] { ние дополнительного кода) }
Else
Operand:=Operand + [i];
End { FromNumberToSet };
{ ------------------------------------------------------- }
Procedure StartPosMemory;
{ Установка памяти в начальное состояние }
Var i : byte;
Begin
For i:=0 to MaxAddress do
Memory[i]:=[];
End { StartPosMemory };
{ ------------------------------------------------------- }
Procedure StartPosRegisters;
{ Установка регистров в начальное состояние }
Var i : byte;
Begin
SAK:=0; RA:=0; Aisp:=0;
RK.KOP:=[]; RK.MA:=[]; RK.NR:=[]; RK.AM:=[];
RS :=[]; Res:=[]; OP1:=[]; OP2:=[]; RP:=[];
For i:=0 to 3 do
Reg[i]:=[];
End { StartPosRegisters };
{ ------------------------------------------------------- }
Procedure WriteMemory(Adr1,Adr2:byte);
{ Вывод на экран области памяти Memory, ограниченной }
{ адресами Adr1 и Adr2 (процедура используется при }
{ отладке эмулятора) }
Var i,j,k : byte;
S1 : string80;
Begin
ClrScr; i:=Adr1;
While i<= Adr2 do
Begin
S1:=''; Slovo:=Memory[i];
For j:=1 to 8 do
If j in Slovo then
S1:=S1+'1'
Else
S1:=S1+'0';
S1:=S1+' '; Slovo:=Memory[i+1];
For j:=1 to 8 do
If j in Slovo then
S1:=S1+'1'
Else
S1:=S1+'0';
Inc(i,2); Writeln(S1);
End;
End { WriteMemory };
{ ------------------------------------------------------- }
Procedure WriteRegisters;
{ Вывод содержимого регистров (процедура используется }
{ при отладке эмулятора) }
Var i,j : byte;
Begin
Write('SAK=',SAK); Write(' NumReg=',NumReg);
Writeln(' Aisp=',Aisp);
Write('RK.KOP=');
For i:=1 to 4 do
If i in RK.KOP then
Write('1')
Else
Write('0');
Write(' '); Write('RK.MA=');
For i:=1 to 2 do
If i in RK.MA then
Write('1')
Else
Write('0');
Write(' '); Write('RK.NR=');
For i:=1 to 2 do
If i in RK.NR then
Write('1')
Else
Write('0');
Write(' '); Write('RK.AM=');
For i:=1 to 8 do
If i in RK.AM then
Write('1')
Else
Write('0');
Writeln; Write('RS= ');
For i:=1 to 8 do
If i in RS then
Write('1')
Else
Write('0');
Writeln;
For i:=0 to 3 do
Begin
Write('Reg[',i,']= ');
For j:=1 to 16 do
If j in Reg[i] then
Write('1')
Else
Write('0');
Writeln;
End;
Write('OP1= ');
For i:=1 to 16 do
If i in OP1 then
Write('1')
Else
Write('0');
FromSetToNumber(OP1,Number1);
Writeln(' ',Number1); Write('OP2= ');
For i:=1 to 16 do
If i in OP2 then
Write('1')
Else
Write('0');
FromSetToNumber(OP2,Number1);
Writeln(' ',Number1); Write('Res= ');
For i:=1 to 16 do
If i in Res then
Write('1')
Else
Write('0');
FromSetToNumber(Res,Number1);
Writeln(' ',Number1); Write('RP= ');
For i:=1 to 4 do
If i in RP then
Write('1')
Else
Write('0');
End { WriteRegisters };
{ ------------------------------------------------------- }
End.
$R+
UNIT InsUnit;
{ Процедуры выполнения машинных команд }
Interface
Uses Crt,Dos,DesUnit,BasUnit,Printer;
Procedure Load;
Procedure Store;
Procedure Add;
Procedure Subtract;
Procedure Multiple;
Procedure Divide;
Procedure Conjunct;
Procedure Disjunct;
Procedure Exclus;
Procedure ShiftLeft;
Procedure ShiftRight;
Procedure BranchCond;
Procedure Compare;
Procedure ToSubroutine;
Procedure FromSubroutine;
Implementation
-------------------------------------------------------------
Procedure ExecAddress;
{ Формирование исполнительного адреса }
Var i : byte;
BufSet : set16;
Begin
BufSet:=[]; { пересылка АM в 16-разрядное }
For i:=1 to 8 do { множество BufSet и преобра- }
If i in RK.AM then { зование AM в число Number1 }
BufSet:=BufSet+[i+8];
FromSetToNumber(BufSet,Number1);
If RK.MA=[] then { прямая адресация }
Aisp:=Number1
Else
If RK.MA=[2] then { косвенная адресация }
Begin
RS:=Memory[Number1+1];
BufSet:=[];
For i:=1 to 8 do
If i in RS then
BufSet:=BufSet+[i+8];
FromSetToNumber(BufSet,Number1);
Aisp:=Number1;
End
Else
If RK.MA=[1,2] then { индекс-адресация }
Begin
FromSetToNumber(Reg[0],Number2);
Aisp:=Number1+Number2
End;
End { ExecAddress };
-------------------------------------------------------------
Procedure MakeOR2;
{ Формирование содержимого регистра OP2 }
Var i : byte;
Begin
If RK.MA=[1] then { непосредственная адресация: }
Begin { пересылка AM в OR2 }
OR2:=[];
For i:=1 to 8 do
If i in RK.AM then
OR2:=OR2 + [i+8]
End
Else { другие типы адресации: }
Begin { пересылка двух смежных }
ExecAddress; RA:=Aisp; { ячеек памяти в OR2 }
RS:=Memory[RA];
OR2:=[]; OR2:=OR2 + RS;
RA:=RA+1; RS:=Memory[RA];
For i:=1 to 8 do
If i in RS then
OR2:=OR2 + [i+8];
End;
End { MakeOR2 };
-------------------------------------------------------------
Procedure Load;
{ Реализация команды загрузки 0001 }
Var i : byte;
Begin
MakeOr2;
Res:=OR2; Reg[NumReg]:=Res;
RP:=[];
If Or2=[] then
RP:=RP+[1]
Else
If 1 in OR2 then
RP:=RP+[2]
Else
RP:=RP+[3];
End { Load };
-------------------------------------------------------------
Procedure Store;
{ Реализация команды записи в память 0010 }
Var i : byte;
Begin
ExecAddress; RA:=Aisp;
RS:=Reg[NumReg]; Memory[RA]:=RS;
RA:=RA+1; RS:=[];
For i:=9 to 16 do
If i in Reg[NumReg] then
RS:=RS+[i-8];
Memory[RA]:=RS;
End { Store };
-------------------------------------------------------------
Procedure Add;
{ Реализация команды сложения 0011 }
Var x,y,z : real;
S1,S2 : string80;
Begin
MakeOR2; OR1:=Reg[NumReg];
FromSetToNumber(OR1,Number1);
FromSetToNumber(OR2,Number2);
x:=Number1; y:=Number2; z:=x+y;
Number1:=Number1+Number2;
If (z<-MaxNumber-1) or (z>MaxNumber) then
Begin
RP:=[4];
Number1:=SAK-2; Str(Number1,S1);
S2:=' Переполнение с фикс.зпт Адрес = '+S1;
PrintError(S2);
End
Else
If Number1=0 then
RP:=[1]
Else
If Number1<0 then
RP:=[2]
Else
RP:=[3];
FromNumberToSet(Number1,Res);
Reg[NumReg]:=Res;
End { Add };
-------------------------------------------------------------
Procedure Subtract;
{ Реализация команды вычитания 0100 }
Begin
End { Subtract };
-------------------------------------------------------------
Procedure Multiple;
{ Реализация команды умножения 0101 }
Begin
End { Multiple };
-------------------------------------------------------------
Procedure Divide;
{ Реализация команды деления 0110 }
Begin
End { Divide };
-------------------------------------------------------------
Procedure Conjunct;
{ Реализация команды конъюнкции 0111 }
Begin
MakeOPR;
OR1:=Reg[NumReg]; Res:=OR1*OR2;
If Res=[] then
RP:=[1]
Else
RP:=[2];
Reg[NumReg]:=Res;
End { Conjunct };
-------------------------------------------------------------
Procedure Disjunct;
{ Реализация команды дизъюнкции 1000 }
Begin
End { Disjunct };
-------------------------------------------------------------
Procedure Exclus;
{ Реализация ком-ды исключ. ИЛИ (сложения по модулю 2) 1001 }
Begin
End { Exclus };
-------------------------------------------------------------
Procedure ShiftLeft;
{ Реализация команды сдвига влево 1010 }
Var i : byte;
BufSet : set16;
Begin
If RK.MA=[1] then
Begin
OR1:=[];
For i:=1 to 8 do
If i in RK.AM then
OR1:=OR1+[i+8];
FromSetToNumber(OR1,Number1);
Aisp:=Number1;
End
Else
ExecAddress;
OR1:=Reg[NumReg]; BufSet:=[];
For i:=Aisp+1 to 16 do
If i in OR1 then
BufSet:=BufSet+[i-Aisp];
OR1:=BufSet; Res:=OR1;
If Res=[] then
RP:=[1]
Else
RP:=[2];
Reg[NumReg]:=Res;
End { ShiftLeft };
-------------------------------------------------------------
Procedure ShiftRight;
{ Реализация команды сдвига вправо 1011 }
Begin
End { ShiftRight };
-------------------------------------------------------------
Procedure BranchCond;
{ Реализация команды перехода 1100 }
Var i : byte;
Maska : set4;
Begin
Maska:=[]; Maska:=Maska+RK.Ma;
For i:=1 to 2 do
If i in RK.NR then
Maska:=Maska+[i+2];
RK.MA:=[2]; ExecAddress;
If Maska=[1..4] then
SAK:=Aisp
Else
Begin
Maska:=Maska*RP;
If Maska<>[] then
SAK:=Aisp;
End;
End { BranchCond };
-------------------------------------------------------------
Procedure Compare;
{ Реализация команды сравнения 1101 }
Begin
End { Compare };
-------------------------------------------------------------
Procedure ToSubroutine;
{ Реализация команды перехода к подпрограмме 1110 }
Begin
Number1:=SAK;
FromNumberToSet(Number1,Res);
Reg[NumReg]:=Res;
ExecAddress; SAK:=Aisp;
End { ToSubroutine };
-------------------------------------------------------------
Procedure FromSubroutine;
{ Реализация команды выхода из подпрограммы 1111 }
Begin
OP1:=Reg[NumReg];
FromSetToNumber(OP1,Number1);
SAK:=Number1;
End { FromSubroutine };
-------------------------------------------------------------
End.
{$R+}
UNIT RegUnit;
{ Процедуры реализации режимов работы эмулятора }
Interface
Uses Crt,Dos,DesUnit,BasUnit,InsUnit,Printer;
Procedure InputModul;
Procedure Run;
Procedure Step;
Procedure WriteStore;
Procedure From10To16;
Implementation
-------------------------------------------------------------
Procedure FileName(Var Sn:string12);
{ Формирование имени файла: просмотр текущего каталога; выбор }
{ имен всех файлов с расширением dat; формирование меню с }
{ именами файлов; выбор позиции в меню; запись в строку вы- }
{ бранного имени файла }
Begin
End { FileName };
-------------------------------------------------------------
Procedure InputModul;
{ Ввод исходного модуля машинной программы }
Const Dig = '0123456789ABCDEF';
Bin : array[1..16] of string4 =
('0000','0001','0010','0011','0100','0101','0110','0111',
'1000','1001','1010','1011','1100','1101','1110','1111');
Var i,k,k1,k2 : byte;
ch : char;
Sn : string12;
DirName,S1,S2 : string80;
Begin
FileName(Sn);
If Key=27 then
Begin
Key:=0; Exit
End;
GetDir(0,DirName); S1:=DirName+'\'+Sn;
Assign(FileModul,S1); Reset(FileModul);
Readln(FileModul,SAK); k:=SAK;
While not eof(FileModul) do
Begin
Readln(FileModul,S1);
k1:=NotSpace(S1,1); k2:=Space(S1,k1+1);
If k2=0 then
k2:=length(S1)+1;
S2:=Copy(S1,k1,k2-k1);
S1:='';
For i:=1 to 4 do
Begin
ch:=S2[i]; k1:=Pos(ch,Dig);
S1:=S1+Bin[k1];
End;
Slovo:=[];
For i:=1 to 8 do
If S1[i]='1' then
Slovo:=Slovo+[i];
Memory[k]:=Slovo;
Inc(k); Slovo:=[];
For i:=9 to 16 do
If S1[i]='1' then
Slovo:=Slovo+[i-8];
Memory[k]:=Slovo; Inc(k);
End;
SAK1:=SAK;
Close(FileModul);
WriteXY(MesReg,1,25,BlueWhite); WaitEnter;
End { InputModul };
-------------------------------------------------------------
Procedure Run;
{ Непрерывный режим выполнения машинной программы }
Var i,Switch : byte;
Begin
CondExec:=true;
While CondExec do
Begin
RK.KOP:=[]; RK.MA:=[]; RK.NR:=[]; RK.AM:=[];
RS:=Memory[SAK]; RK.KOP:=RK.KOP+RS;
If RK.KOP<>[] then
Begin
For i:=5 to 6 do
If i in RS then
RK.MA:=RK.MA+[i-4];
For i:=7 to 8 do
If i in RS then
RK.NR:=RK.NR+[i-6];
RS:=Memory[SAK+1]; RK.AM:=RS;
NumReg:=0; { перевод значения номера }
If 1 in RK.NR then { регистра RK.NR в числовую }
Inc(NumReg,2); { форму }
If 2 in RK.NR then
Inc(NumReg);
Inc(SAK,2);
Switch:=0; { перевод значения кода }
For i:=1 to 4 do { операций в числовую форму }
Begin
Switch:=2*Switch;
If i in RK.KOP then
Switch:=Switch+1;
End;
Case Switch of
1 : Load;
2 : Store;
3 : Add;
4 : Subtract;
5 : Multiple;
6 : Divide;
7 : Conjunct;
8 : Disjunct;
9 : Exclus;
10 : ShiftLeft;
11 : ShiftRight;
12 : BranchCond;
13 : Compare;
14 : ToSubroutine;
15 : FromSubroutine;
end;
End
Else
CondExec:=false
End;
WriteXY(MesReg,1,25,BlueWhite); WaitEnter;
End { Run };
-------------------------------------------------------------
Procedure Step;
{ Шаговый режим выполнения машинной программы }
Begin
End { Step };
-------------------------------------------------------------
Procedure WriteStore;
{ Вывод на экран содержимого оперативной памяти Memory }
Begin
End { WriteStore };
-------------------------------------------------------------
Procedure From10To16;
{ Перевод целого положительного числа из 10 с/с в 16 с/с }
Begin
End { From10To16 };
------------------------------------------------------------ }
End.
{$R+}
Program Emulat;
{ Основная программа эмулятора }
Uses Crt,Dos,DesUnit,BasUnit,RegUnit,InsUnit;
{ ---------------------------------------------------------- }
Procedure Title;
{ Формирование заставки }
Begin
End { Title };
{ ---------------------------------------------------------- }
Procedure MakeMenu;
{ Формирование основного меню }
Const
LeftBorder = 18; { левая граница рамки }
UpBorder = 6; { верхняя граница рамки }
MenuCount = 6; { кол-во позиций меню }
LenSt = 32; { длина строки позиции меню }
St : array[1..MenuCount] of string[LenSt] =
('Ввод исходных данных',
'Полное выполнение программы',
'Шаговое выполнение программы',
'Вывод области памяти',
'Преобразование 10 --> 16',
'Выход из меню');
Var i, Switch : byte;
Begin
TextBackGround(Cyan); ClrScr; { очистка экрана }
SetCursor($2000); { отключение курсора }
{ теневая рамка }
Frame(LeftBorder,UpBorder,LeftBorder+LenSt+6,
UpBorder+2*MenuCount+2,3,Blue);
{ основная рамка }
Frame(LeftBorder+1,UpBorder+1,LeftBorder+LenSt+7,
UpBorder+2*MenuCount+3,2,LightGrayBlack);
WriteXY(' Р Е Ж И М Р А Б О Т Ы ',LeftBorder+9,
UpBorder+1,LightGrayBlack);
For i:=1 to MenuCount do
WriteXY(St[i],LeftBorder+5,2*i+UpBorder+1,
LightGrayBlack);
S:=FillString(80,' ');
WriteXY(S,1,24,MagentaWhite);
WriteXY(S,1,25,BlueLightGray);
TextAttr:=CyanBlack;
GotoXY(1,1); InsLine;
WriteLastString(Mes1,BlueBlueLightRed,
BlueLightGray);
Repeat
MoveFromScreen(BufScreen1,1,25); { сохранение экрана }
SetCursor($2000); { отключение курсора }
CurrRow:=9; CurrCol:=21; { нач.значение позиции экрана }
{ цветовое выделение начальной позиции }
InsertColorString(CurrRow,CurrCol,34,BlueYellow);
{ управление передвижением строки-курсора }
MoveCursor(9,19,2,21,21,0,34,BlueYellow,LightGrayBlack);
If Key=27 then
Begin
TextBackGround(Cyan); ClrScr; Exit;
End;
Switch:=(CurrRow-UpBorder-1) div 2;
Case Switch of
1 : InputModul; { Ввод исходного файла }
2 : Run; { Полное выполнение программы }
3 : Step; { Шаговое выполнение программы }
4 : WriteStore; { Вывод содержимого памяти }
5 : From10To16; { Преобразование 10 16 }
6 : Begin { Выход из меню }
TextBackGround(Cyan); ClrScr; Exit;
End;
end;
MoveToScreen(BufScreen1,1,25); {восстановление }
{ экрана }
Until false;
SetCursor($0607); { включение курсора }
End { MakeMenu };
{ ---------------------------------------------------- }
Begin
{ Формирование заставки }
Title;
If Key=27 then
Exit
Else
Key:=0;
{ Очистка памяти и регистров }
StartPosMemory;
StartPosRegisters;
{ Формирование меню и выбор режима работы программы }
MakeMenu;
End.
Приложение 4
ПРОГРАММА ОРГАНИЗАЦИИ СКРОЛЛИНГА
В программе Scrolling при нажатии клавишей Up или Down производится "прокручивание" вверх или вниз текста, записанного в массиве строк St. Для организации скроллинга применяются процедуры InsLine и DelLine. Текст в массиве St предварительно заполняется символами, выбираемыми случайным образом из таблицы ASCII, для чего используется генератор случайных чисел Random.
Program Scrolling;
{ Скроллинг текста в окне экрана }
Uses Crt,Dos;
Const
NumStringMax = 100; { макс.размер массива строк }
LeftBorder = 17; { абсцисса левого верхнего угла окна }
UpBorder = 8; { ордината левого верхнего угла окна }
WindowLength = 43; { длина окна }
WindowHeight = 12; { высота окна }
Type
WindowString = string[WindowLength-1];
StringAr = array[1..NumStringMax] of WindowString;
Var i,j,k,
Key, { код клавиши }
NumString : byte; { номер элемента массива St,который }
Cond : boolean; { расположен в первой строке окна }
St : StringAr; { массив строк }
{ ---------------------------- }
Procedure SetCursor(Cursor:word);
{ Управление курсором }
Var Reg : Registers;
Begin
With Reg do
Begin
AH:=1; BH:=0;
CH:=Hi(Cursor); CL:=Lo(Cursor);
End;
Intr($10,Reg);
End { SetCursor };
{ --------------------------------- ]
Function GetKey : byte;
{ Чтение символа клавиатуры }
Var ch : char;
Begin
ch := ReadKey;
If ord(ch) = 0 then { Расширенный код символа }
GetKey := ord(ReadKey)
Else
GetKey := ord(ch); { Нормальный код символа }
End { GetKey };
----------------------------------------------------------
Begin
TextBackGround(Black);
ClrScr;
{ Формирование массива строк }
Randomize;
For i:=1 to NumStringMax do
Begin
St[i]:=' ';
For j:=1 to WindowLength-1 do
Begin
k:=Random(200)+32;
St[i]:=St[i]+chr(k);
End;
End;
{ Формирование окна }
Window(LeftBorder,UpBorder,LeftBorder+WindowLength-1,
UpBorder+WindowHeight-1);
TextAttr:=Blue + Brown shl 4;
ClrScr;
SetCursor($2000); { Отключение курсора ]
{ Начальное заполнение окна текстом из массива St }
For i:=1 to WindowHeight do
Begin
GotoXY(1,i); Write(St[i]);
End;
NumString:=1;
{ Управление скроллингом текста }
Repeat
Key:=GetKey; Cond:=false;
Case Key of
13,27 : Cond := true; Enter,Escape
72 : If NumString <= NumStringMax - WindowHeight then
Begin { Up }
GotoXY(1,1); DelLine;
GotoXY(1,WindowHeight);
Write(St[NumString+WindowHeight]);
Inc(NumString);
End;
80 : If NumString > 1 then { Down }
Begin
GotoXY(1,1); InsLine;
Write(St[NumString-1]);
Dec(NumString);
End;
end;
Until Cond;
SetCursor($0607); { Включение курсора }
End.
Приложение 5
ЦВЕТОВЫЕ атрибутЫ
Структура байта атрибутов:
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
½ B ½ b a c k ½ H ½ t e x t ½
Здесь back - цвет фона, text - цвет символа. Каждый разряд в разделах back и text - это один из основных цветов монитора: красный, зеленый или синий. Смешивание основных цветов дает 8 цветовых комбинацій. H - разряд интенсивности цвета. С учетом интенсивности получаем 16 цветов для отображения символа (темные и светлые). Для фона возможны только темные цвета. Разряд B управляет мерцанием символа: если B = 1, символ мигает с частотой около 2 гц.
К видеопамяти можно обращаться из программы с помощью адреса. Для этого в ПЭОМ зарезервированы адреса с сегментной частью от $A000 до $DFFF, то-есть 256 Кбайт.
В стандартном модуле Crt для текстового режима экрана определены константы для всех 16 значений цветового атрибута в соответствии с приведенной ниже таблицей.
Бит Н | Бит цвета кр. зел. син. | Десятичное значение | Имя константы | Цвет | ||
0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 | 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1 | 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 | 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 | 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | Black Blue Green Cyan Red Magenta Brown LightGray DarkGray LightBlue LightGreen LightCyan LightRed LightMagenta Yellow White | Черный Синий Зеленый Голубой Красный Фиолетовый Коричневый Светло-серый Темно-серый Ярко-синий Ярко-зеленый Ярко-голубой Розовый Малиновый Желтый Белый |
128 | Blink | Мерцание символа | ||||
Приложение 6
Т А Б Л И Ц А A S C I I
При формировании в программе рамок, таблиц и т.п. зачастую используются символы псевдографики. Коды этих символов содержатся в ASCII-таблицах, приводимых в справочной литературе.
При отсутствии справочных материалов для определения ASCII-кода любого символа можно использовать приведенную ниже программу ASCII.
Program ASCII;
{ Символы таблицы ASCII }
Uses Crt;
Const
SpecialChars: set of char =
[#7,#8,#10,#13];
ch : char = #0;
Var
i,k : byte;
S : string;
{ ------------------------- }
Procedure WaitEnter;
Var ch : char;
Begin
Repeat
ch:=ReadKey;
Until ord(ch)=13;
End { WaitEnter };
{ ------------------------- }
Procedure Symbols;
Var i : byte;
Begin
For i:=1 to 10 do
Begin
If ch in SpecialChars then
Write(' ')
Else
Write(ch,' ');
If ch=#255 then
Begin
WaitEnter; Halt
End;
ch:=succ(ch);
End;
End { Symbols };
{ ------------------------- }
Begin
ClrScr; k:=0;
For i:=1 to 13 do
Begin
Str(k:3,S); Write(S,' ');
Symbols; Inc(k,10);
Str(k:3,S); Write('',S,' ');
Symbols; Inc(k,10);
Writeln; Writeln;
End;
End.
Приложение 7
РАСШИРЕННЫЕ КОДЫ КЛАВИШЕЙ
При формировании различного вида меню, строк статуса и т.п. используются специальные клавиши (функциональные, перемещения курсора, сочетания клавиши Alt или Ctrl с другими клавишами и т.д.). Кодирование таких клавишей осуществляется расширенным ASCII-кодом, который представляется двухбайтными последовательностями. Первый байт является служебным и равен нулю, второй байт - информационный. С помощью расширенных ASCII-кодов кодируются сигналы следующих клавишей и их комбинаций:
- функциональных (F1..F10);
- удаления и вставки (Del и Ins);
- перемещения курсора (стрелки, Home, End, PgDn, PgUp, BackSpace);
- Alt + (другая клавиша);
- Ctrl + (другая клавиша);
- Shift+Tab.
Таблицы расширенных кодов клавишей приведены в справочной литературе. При отсутствии справочных материалов для определения кода любой клавиши или комбинации клавишей можно использовать приведенную ниже программу KeyTest.
Program KeyTest;
{ Коды клавишей и их комбинаций }
Uses Crt;
Var ch1,ch2 : char;
Begin
ClrScr;
Repeat
ch1:=ReadKey;
Writeln('ch1= ',ch1,' ord(ch1)= ',ord(ch1));
If ord(ch1)=0 then
Begin
ch2:=ReadKey;
Writeln('ch2= ',ch2,' ord(ch2)= ',
ord(ch2));
End;
Until ord(ch1)=13;
End.
С О Д Е Р Ж А Н И Е
Стр.
Введение ............................................ 3
1. Содержание курсовой работы ....................... 3
2. Учебная вычислительная машина М1 ................. 4
2.1. Архитектура ЭВМ М1 ........................... 4
2.2. Выполнение команд в учебной ЭВМ М1 ........... 7
2.3. Составление программы на машинном языке ЭВМ М1 11
3. Разработка эмулятора ............................. 20
3.1. Программные модели узлов ЭВМ М1 .............. 20
3.2. Ввод и преобразование исходных данных ........ 22
3.3. Прямое и обратное преобразование строки или
множества в число ............................ 25
3.4. Индикация переполнения с фиксированной запятой 29
3.5. Режимы работы эмулятора ...................... 29
3.6. Формирование меню ............................ 31
3.7. Организация скроллинга ....................... 33
3.8. Структура эмулятора .......................... 34
3.9. Стиль программирования ....................... 35
3.10. Описание эмулятора ЭВМ М1 ................... 37
3.11. Рекомендации по разработке эмулятора ........ 39
3.12. Содержание пояснительной записки ............. 40
Литература ........................................... 41
Приложение 1. Техническое задание на курсовое
проектирование 42
Приложение 2. Варианты заданий ....................... 44
Приложение 3. Текст эмулятора ЭВМ М1 ................. 55
Приложение 4. Программа организации скроллинга ....... 78
Приложение 5. Цветовые атрибуты ...................... 80
Приложение 6. Таблица ASCII .......................... 81
Приложение 7. Расширенные коды клавишей .............. 83