Различия между версиями 9 и 11 (по 2 версиям)
Версия 9 от 2019-04-19 08:22:24
Размер: 42941
Редактор: FrBrGeorge
Комментарий:
Версия 11 от 2019-05-17 15:26:00
Размер: 43585
Редактор: FrBrGeorge
Комментарий:
Удаления помечены так. Добавления помечены так.
Строка 27: Строка 27:
 {{{  {{{#!highlight nasm
Строка 55: Строка 55:
 В больших многофайловых проектах принято все макросы складывать в отдельный файл и включать их в код программы с помощью директивы '''.include файл_с_макросами''' . Подпрограммы при этом складываются в другой файл (возможно. не один), т. н. «библиотеку», и подключаются посредством многофайловой сборки. На предыдущем примере: Файл с программой '''prog.asm''':
 {{{
В больших многофайловых проектах принято все макросы складывать в отдельный файл и включать их в код программы с помощью директивы '''.include файл_с_макросами''' . Подпрограммы при этом складываются в другой файл (возможно. не один), т. н. «библиотеку», и подключаются посредством многофайловой сборки. На предыдущем примере: Файл с программой '''prog.asm''':
{{{#!highlight nasm
Строка 67: Строка 67:
 Файл с подпрограммами '''lib.asm''':
 {{{
Файл с подпрограммами '''lib.asm''':
{{{#!highlight nasm
Строка 90: Строка 90:
 На забываем метки всех подпрограмм, которые понадобятся в других файлах, объявлять как '''.globl''' Файл с макросами '''macro.inc''' (имя файла не заканчивается на .asm в знак того, что его ''не нужно'' транслировать отдельно):
 {{{
Не забываем метки всех подпрограмм, которые понадобятся в других файлах, объявлять как '''.globl''' Файл с макросами '''macro.inc''' (имя файла не заканчивается на .asm в знак того, что его ''не нужно'' транслировать отдельно):
{{{#!highlight nasm
Строка 131: Строка 131:
 {{{  {{{#!highlight nasm
Строка 223: Строка 223:
 . Чтобы Mars «увидел» устройство, нужно «подключить» его нажатием кнопки «Connect to MIPS». Чтобы Mars «увидел» устройство, нужно «подключить» его нажатием кнопки «Connect to MIPS».
Строка 229: Строка 229:
{{{ {{{#!highlight nasm
Строка 262: Строка 262:
 {{{  {{{#!highlight nasm
Строка 272: Строка 272:
 . {{{ {{{#!highlight nasm
Строка 337: Строка 337:
{{{ {{{#!highlight nasm
Строка 354: Строка 354:
Дисплей можно «привязать» к нескольким различным местам «настоящей» памяти (т. е. сделать видеопамять доступной посредством MMIO).

Зададим константами размеры дисплея и базовый адрес

{{{#!highlight nasm
.eqv BASE 0x10010000
.eqv WIDTH 512
.eqv HEIGHT 256
}}}
Строка 356: Строка 365:
 {{{ {{{#!highlight nasm
Строка 362: Строка 371:
 Напишем подпрограмму рисования точки по заданным координатам. Точка будет рисоваться текущим цветом, а заданные координаты — сохраняться.

 
{{{
Напишем подпрограмму рисования точки по заданным координатам. Точка будет рисоваться текущим цветом, а заданные координаты — сохраняться.
{{{#!highlight nasm
Строка 376: Строка 384:
 Для написания более сложных функций подготовим несколько макросов: '''push''' и '''pop; '''пролог '''subroutine '''и эпилог '''return''' для написания универсальных подпрограмм согласно конвенции, а также «обёртку» '''hrandom '''вокруг системного вызова Mars для генерации случайного числа заданного диапазона размером в полуслово:

 
{{{
Для написания более сложных функций подготовим несколько макросов: '''push''' и '''pop; '''пролог '''subroutine '''и эпилог '''return''' для написания универсальных подпрограмм согласно конвенции, а также «обёртку» '''hrandom '''вокруг системного вызова Mars для генерации случайного числа заданного диапазона размером в полуслово:
{{{#!highlight nasm
Строка 426: Строка 433:
 Первый параметр системного вызова Mars № 42 — т. н. «номер случайной последовательности», достаточно, чтобы он был равен 0.

Напишем подпрограмму рисования отрезка из текущей точки в заданную:

 
{{{
Первый параметр системного вызова Mars № 42 — т. н. «номер случайной последовательности», достаточно, чтобы он был равен 0. Напишем подпрограмму рисования отрезка из текущей точки в заданную:
{{{#!highlight nasm
Строка 465: Строка 469:
 Подпрограмма запоминает заданную конечную точку отрезка в качестве текущей. Получается нечто вроде «черепашьей графики».

 Наконец, напишем [[attachment:bitmapM.asm|программу, заполняющую дисплей отрезками случайного цвета]](для того, чтобы случайный цвет в пространстве RGB оказался достаточно ярким, пришлось написать специальную подпрограмму):

 
{{{
Подпрограмма запоминает заданную конечную точку отрезка в качестве текущей. Получается нечто вроде «черепашьей графики».

Наконец, напишем [[attachment:bitmapM.asm|программу, заполняющую дисплей отрезками случайного цвета]](для того, чтобы случайный цвет в пространстве RGB оказался достаточно ярким, пришлось написать специальную подпрограмму):
{{{#!highlight nasm
Строка 503: Строка 506:
 Программа начинается с метки '''main''', так что при сборке надо включить «Initialize Program Counter to global 'main' if defined» в настройке Mars.

 В силу природы MMIO прямая запись в видеопамять — операция долгая и ресурсоёмкая. Это видно, например, из того, с какой скоростью Mars может записывать слова в оперативную память, и с какой — последовательно заполнять точки графического дисплея. В то же время именно графические устройства требуют очень быстрой работы с очень большими объёмами данных

 
* Устройство снабжается собственной памятью и DMA-интерфейсом, и вместо MMIO большие объёмы данных пересылаются по DMA
Программа начинается с метки '''main''', так что при сборке надо включить «Initialize Program Counter to global 'main' if defined» в настройке Mars.

{{attachment:Lines.png}}

В силу природы MMIO прямая запись в видеопамять — операция долгая и ресурсоёмкая. Это видно, например, из того, с какой скоростью Mars может записывать слова в оперативную память, и с какой — последовательно заполнять точки графического дисплея. В то же время именно графические устройства требуют очень быстрой работы с очень большими объёмами данных
 * Устройство снабжается собственной памятью и DMA-интерфейсом, и вместо MMIO большие объёмы данных пересылаются по DMA

Долги за прошлую лекцию

Имитация исключений

С одной стороны, на все случаи жизни исключения предусмотреть нельзя.

С другой стороны, это очень эффективный механизм обращения к ОС почти (или совсем) без дополнительных действий, в отличие от syscall. Системный обработчик по адресу 0x80000180 вызывается немедленно одной инструкцией, атомарно с операцией сравнения.

teq $t1,$t2

Trap if equal

Trap if $t1 is equal to $t2

teqi $t1,-100

Trap if equal to immediate

Trap if $t1 is equal to sign-extended 16 bit immediate

tge $t1,$t2

Trap if greater or equal

Trap if $t1 is greater than or equal to $t2

tgei $t1,-100

Trap if greater than or equal to immediate

Trap if $t1 greater than or equal to sign-extended 16 bit immediate

tgeiu $t1,-100

Trap if greater or equal to immediate unsigned

Trap if $t1 greater than or equal to sign-extended 16 bit immediate, unsigned comparison

tgeu $t1,$t2

Trap if greater or equal unsigned

Trap if $t1 is greater than or equal to $t2 using unsigned comparision

tlt $t1,$t2

Trap if less than

Trap if $t1 less than $t2

tlti $t1,-100

Trap if less than immediate

Trap if $t1 less than sign-extended 16-bit immediate

tltiu $t1,-100

Trap if less than immediate unsigned

Trap if $t1 less than sign-extended 16-bit immediate, unsigned comparison

tltu $t1,$t2

Trap if less than unsigned

Trap if $t1 less than $t2, unsigned comparison

tne $t1,$t2

Trap if not equal

Trap if $t1 is not equal to $t2

tnei $t1,-100

Trap if not equal to immediate

Trap if $t1 is not equal to sign-extended 16 bit immediate

Таким образом гарантируется, что за время, прошедшее между сравнением и обращением к системе, никакие данные не изменятся (потому что этого времени нет :) ).

Назначение инструкций типа trap:

  • Отладочный останов программы (т. н. assertion). Допустим, в регистре не должно получаться 0, а если ошибочно получится, то лучше пускай система обработает эту ошибку, чем программа будет дальше считать. Например, можно проверить, что изменение счётчика в цикле вообще происходит:
       1         teqi    $t1 0
       2         subu    $t0 $t0 $t1
       3         bgez    $t0 loop
    
  • Обработка исключительной ситуации, если аппаратного обнаружения её нет. Обычно используется команда R-типа, в которой можно в неиспользуемом аппаратно поле хранить причину исключения. Ср. архитектура RISCore:

    TEQ - Ловушка по равенству

    31

    25

    20

    15

    5

    SPECIAL 000000

    rs

    rt

    code

    TEQ 110100

Содержимое поля code игнорируется системой и может быть использовано для кодированиия информации для системного ПО. Для получения этой информации программа должна загрузить слово команды из памяти (адрес хранится в регистре EPC сопроцессора 0). В ассемблере Mars эта возможность отсутствует, обидно.

Свойства макроассемблера MARS

Замечания авторов MARS относительно их макроассемблера:

  • Макроопределение должно идти в тексте программы до соответствующей макрокоманды (иначе пришлось бы анализировать текст дважды)

  • Макроопределение локально в пределах одного файла. Это с очевидностью вытекает из самого процесса макроподстановки перед трансляцией. Если нужно, чтобы один и тот же макрос был виден из нескольких файлов, используйте .include

  • Вложенные макросы не поддерживаются, т. е. внутри макроопределения не может встречаться директива .macro

  • Внутри макроопределения, как и в тексте программы, могут встречаться только ранее определённые макрокоманды, искать их определения далее по тексту никто не будет
  • Все метки меняются в процессе макроподстановки, превращаясь в метка_M№

    • (Замечание от меня: нет, на все! если передать метку в качестве параметра, а потом написать что-то вроде %label: , _M№ к ней не припишется. Не знаю, как и зачем это можно использовать…)

  • Несколько макроса с одинаковым именем, но разным количеством параметров, считаются различными, и их можно использовать все
  • Повторное определение макроса с тем же именем и тем же количеством параметров игнорируется, макрокоманда раскрывается в первое определение
  • Параметром макроса (в силу ограниченной реализации) может быть только атомарная лексема языка ассемблера. Например, параметром не может быть "4($t0)", потому что это две лексемы, а не одна

  • Макросредства ассемблера не входят ни в какой стандарт и остаются на усмотрение авторов ассемблера

В больших многофайловых проектах принято все макросы складывать в отдельный файл и включать их в код программы с помощью директивы .include файл_с_макросами . Подпрограммы при этом складываются в другой файл (возможно. не один), т. н. «библиотеку», и подключаются посредством многофайловой сборки. На предыдущем примере: Файл с программой prog.asm:

   1 .include        "macro.inc"
   2 .globl  main
   3 .text
   4 main:
   5         input   "First input" $t0
   6         input   "Second input" $t1
   7         print   "First result" $t0
   8         print   "Second result" $t1
   9         exit

Файл с подпрограммами lib.asm:

   1 .globl  _print _input
   2 
   3 .text
   4 _input: # $a0message / $v0input value
   5         li      $v0 4
   6         syscall
   7         li      $v0 5
   8         syscall
   9         jr      $ra
  10 
  11 _print: # $a0message, $a1number
  12         li      $v0 4
  13         syscall
  14         move    $a0 $a1
  15         li      $v0 1
  16         syscall
  17         li      $a0 10
  18         li      $v0 11
  19         syscall
  20         jr      $ra

Не забываем метки всех подпрограмм, которые понадобятся в других файлах, объявлять как .globl Файл с макросами macro.inc (имя файла не заканчивается на .asm в знак того, что его не нужно транслировать отдельно):

   1 .macro  input   %msg %reg
   2 .data
   3 msg:    .ascii  %msg
   4         .asciiz ": "
   5 .text
   6         la      $a0 msg
   7         jal     _input
   8         move    %reg $v0
   9 .end_macro
  10 
  11 .macro  print   %msg %reg
  12 .data
  13 msg:    .ascii  %msg
  14         .asciiz ": "
  15 .text
  16         la      $a0 msg
  17         move    $a1 %reg
  18         jal     _print
  19 .end_macro
  20 
  21 .macro  exit
  22         li      $v0 10
  23         syscall
  24 .end_macro

Чего нет в Mars

Макроассемблер Mars вполне достаточен для учебных целей, но не реализует много из того, что есть в промышленных средствах программирования на ассемблере

  • Библиотека макросов и подпрограмм. Чтобы написать большую программу, потребуется множество подпрограмм, реализующих стандартные приёмы работы — ввод-вывод, работа с дисками, управление внешними устройствами и т. п. Для этого в профессиональных инструментариях, типа NASM или Gnu Assebmler, имеются заранее подготовленные библиотеки макросов и подпрограмм. А мы пишем их сами :)

  • «Настоящий» макропроцессор. Макропросессор в Mars опирается только на лексемы языка ассемблера и не имеет собственного языка. Это почти не мешает, но временами (как в примере с 4($t0) ) слегка неудобно
  • Адресная арифметика. Вычисление некоторых значений. смещений и размеров на основании уже известных адресов находится в ассемблере Mars в зачаточном состоянии.
    • Мы можем программе написать в качестве адреса Arr+0x200, и макроассемблер подсчитает и вставит в код результат
    • А вот написать что-то вроде ArrEnd-Arr, где ArrEnd — метка конца массива, в Mars уже нельзя, вычислять это в самой программе

  • Переменные периода трансляции. полезно уметь назначать мнемонические имена результатам таких вычислений, чего Mars делать тоже не умеет
    • Например SIZE=ArrEnd-Arr, и потом использование константы SIZE

  • ⇒ Вычисление выражений с использованием переменных и констант. В промышленных макропроцессорах возможно вычисление любых арифметических выражений и задание констант для них. Ещё раз напомним, что всё это происходит до трансляции, и в оттранслированный код попадает результат таких вычислений

  • Условная трансляция. Наиболее полезное свойство вычислений в период трансляции — это возможность транслировать или не транслировать части текста в зависимости от результата этих вычислений. Например можно вставить исходный текст отладочные сообщения, но транслировать их только если определена некоторая переменная периода трансляции DEBUG. Как-нибудь так:
       1 .if DEBUG
       2         код, транслируемый
       3         только если существует
       4         константа DEBUG
       5 .endif
    
  • Генерация макроопределений. Если разрешить создавать макросы внутри макросов, можно развёртывать целые семейства определений в зависимости от исходного параметра внешнего макроса
  • Конкатенация.. Иногда необходимо, чтобы результат постановки нескольких макросов интерпретировался затем как одна лексема языка (например, строка label##suffix##index превращалась при наличии констант suffix=_M и index=5 в label_M5). В Mars такого механизма нет

  • Бывает очень полезно ограничить видимость меток сильнее, чем просто внутри файла. Например, если в файле задано несколько подпрограмм, в каждой из них хотелось бы иметь возможность использовать метки типа start, finish, loop или стандартные имена переменных. Это можно было бы сделать, введя особенный синтаксис временных меток или ограничить видимость меток специальной конструкцией «локальное пространство имён» и т. п. В целом макроассемблер Mars достаточен для написания программ среднего объёма, а написание действительно крупных проектов на языке ассемблера выходит за рамки данного курса

Ввод-вывод

Базовая лекция

Внешние устройства

Внешнее устройство (также периферийное) — любая аппаратура, которая обменивается данными с ЭВМ.

Задачи:

  • Ввод (микрофон, кнопка выключения питания и т. п.)
  • Вывод (колонки, монитор…)
  • Ввод-вывод :) (консоль)

  • Частный случай: хранение = ввод/вывод + полное (или избыточное) кодирование + атрибуция (например, номера секторов на диске)
  • Передача = ввод/вывод + несколько ЭВМ + синхронизация (сетевая карта, …)

⇒ ВУ может быть не сложнее трёх проводов с кнопкой, а может быть целым специализированным компьютером со своим процессором, памятью, регистрами и т. п.

Способы взаимодействия с ВУ на разных архитектурах

  • Унифицированный: ∃ стандарт на команды и логику работы ВУ

    • ВУ слишком разные, общий стандарт запредельно сложен
    • Логику сложных ВУ надо уметь программировать непосредственно при работе (а что вообще тв них за процессоры стоят?)
  • Порты В/В: стандартизуется только способ обращения к ВУ

    • все каналы связи с ВУ (т. н. порты) как-то нумеруются

    • вывод — инструкция out данные номер_порта ; ввод — инструкция in приёмник номер_порта

    • что означают передаваемые и принимаемые данные — зависит от самого устройства и способа его физического подключения (например, один бит = один провод)
  • MMIO: отображение В/В на оперативную память (используется в MIPS)
    • вместо портов используются обычные адреса и команды работы с памятью
    • обращение к адресу из определённого диапазона приводит к обмену данными с ВУ, а не с оперативной памятью
    • соответствие конкретных адресов конкретным интерфейсам ВУ определяется как-то

    • вариант: отображается целая область памяти ВУ (например, страница видеопамяти)

MMIO-регистры бывают

  • управляющими (запись = команда ВУ или изменение его состояния)
  • регистрами данных (запись/чтение данных, соответствующих команде)
  • статусными (только чтение информации о состоянии ВУ)
    • часто встречается т. н. «флаг готовности» — бит, который равен единице ,если устройство готово что-то делать (пришли новые данные в устройство ввода, все данные устройства вывода переданы и т. п.)

Работа процессора с MMIO:

  • Арбитраж (какие операции В/В делать первыми)
  • Сопоставление MMIO-адреса устройству
  • Превращение MMIO-адреса во внутреннюю операцию обмена данными с ВУ
  • Анализ состояния ВУ и отображение на MMIO-ячейке
  • Передача данных от/к ВУ

Взаимодейстие на основе опроса

Поллинг (polling, опрос) — способ работы с внешними устройствами, при котором программа регуларно проверяет готовность устройства к В/В, и если готовность есть, осуществляет соответствующую операцию.

Организация поллинга из программы:

  1. Подготовка устройства к работе
  2. Цикл
    1. Проверка готовности устройства
    2. Если устройство готово
      • Операция В/В
      • Выход из цикла
    3. Если устройство не готово
      • Бессмысленное ожидание
  3. Перевод устройства в исходное состояние

Замечание: пункт «бессмысленное ожидание» (бессмысленная трата процессорного времени!) можно было бы заменить на «выполнение полезных действий», но

  • Всё происходит в процедуре обмена с ВУ, которая может и не знать о ом, какие действия полезны (это вообще может быть подпрограмма, вызываемая из многих мест)

  • «Полезные действия» в этом случае должны длиться строго определённое время (время ожидания готовности). Отмерить бессмысленные действия намного проще.

Цифровой блок Mars

Цифровой блок «Digital Lab Sim» — это воображаемое внешнее устройство для Mars, позволяющее потренироваться в организации ввода и вывода

Выглядит в работе оно так:

DigitalLab.png

0xFFFF0010

command right seven segment display

Биты правого цифрового индикатора (запись)

0xFFFF0011

command left seven segment display

Биты левого цифрового индикатора (запись)

0xFFFF0012

command row number / enable keyboard interrupt

Номер ряда клавиатуры для опроса (биты 0-3)
разрешение прерываний от клавиатуры (7 бит) (запись)

0xFFFF0013

counter interruption enable

Разрешение прерывания № 10 один раз в 30 инструкций (запись)

0xFFFF0014

receive row and column of the key pressed

Результат опроса: бит столбца (7-4), бит ряда (3-0), если клавиша активна (чтение)

Чтобы Mars «увидел» устройство, нужно «подключить» его нажатием кнопки «Connect to MIPS».

Если записать байт в регистр 0xFFFF0010, на правом индикаторе загорятся красным некоторые сегменты, а некоторые станут серыми. Сегментов всего семь, восьмая — точка, так что каждый бит байта отвечает за свою лампочку. Запись 0 погасит все сегменты, запись 0xff — зажжёт. Аналогично для регистра 0xFFFF0011 и левого индикатора. Считать содержимое индикатора нельзя.

Например, при выполнении следующего кода:

   1     lui   $t8 0xffff              # база MMIO
   2     li    $t1 0xdb
   3     sb    $t1 0x10($t8)
   4     li    $t2 0x66
   5     sb    $t2 0x11($t8)

Получим вот такой результат ☺ :

snap-0414-103238.png

Если с выводом в цифровые окошки всё более-менее (а всё-таки, какие биты каким сегментам соответствуют?) понятно, то ввод с клавиатуры на первый взгляд кажется совершенно эзотерическим:

  • Возвращается не номер клавиши, а битовая маски, в которой четыре бита соответствуют колонке и четыре столбцу. Если клавиша активна, два бита будут выставлены в 1
  • Чтобы получить ответ, надо запустить операцию сканирования, а затем считать результат
  • Операцию сканирования надо проводить с каждой строкой клавиатуры по отдельности!

Дело в том, что это устройство спроектировано «как в жизни». Предполагается, что в клавиатуре есть всего 8 проводов (как в матрице памяти) – 4×4 – и всё, что можно сделать — это подать напряжение на один из горизонтальных проводов и отобразить, на какой вертикальный провод он замкнут, если соответствующая клавиша нажата.

Сравнительно несложно обеспечить соответствие провода, на котором обнаружилось напряжение, отдельному биту в регистре, а также произвести простейшие операции над этими битами.

Однако более сложные логические цепочки — шифратор ( для превращения провода N в двоичное число), дешифратор (обратно), сумматор и т. п. — в подобных «железках» обычно отсутствуют.

Вот и выходит, что задача превратить «сырые» данные устройства в осмысленные ложится на программу.

Итак, для того, чтобы просканировать, нажата ли какая-нибудь клавиша в ряду «0-1-2-3», надо:

  1. «Подать напряжение на нулевую строку», то есть записать в 0xffff0012 число, у которого только нулевой бит равен 1 (это число 1)

  2. Считать из регистра 0xffff0014 значение. Если клавиша в нулевом ряду не нажата, вернётся 0, если нажата, вернётся число, в котором

    • установлен в 1 ровно один из первых четырёх битов, соответствующий нулевой строке (так же, как в операции сканирования)
    • установлен в 1 ровно один из битов 4…7, в соответствие со столбцом, в котором находится нажатая клавиша (0x10,0x20,0x40 и 0x80 для клавиш «0», «1», «2» и «3» соответственно)
    • например, для клавиши «2» ответ будет 0x41
  3. Кусок кода при этом может выглядеть так:
       1         li      $t0 1                   # первая строка
       2         sb      $t0 0xffff0012          # «подаём напряжение»
       3         lb      $t0 0xffff0014          # забираем результат
    
  4. Для «подачи напряжения» на другие строки («4-5-6-7», «8-9-a-b» или «c-d-e-f») в 0xffff0012 надо записывать 2, 4 или 8. Из 0xffff0014 будет считываться число, у которого первые четыре бита установлены аналогично (если нажата клавиша в соответствующем ряду, иначе считывается 0)
  5. Если записать в 0xffff0012 число, отличное от 1,2,4 или 8, вернётся всегда 0 (более умное устройство подало бы напряжение на провод «ошибочная операция», который можно было бы прочитать в регистре статуса)

Пример: просканировать цифровую клавиатуру и полученное значение записать в правое окошко. В левое окошко записывается количество нажатий. Оба значения не приводят к каким-то осмысленным изображениям в цифровых окошках, но по правому видно, что результат сканирования имеет активными всегда 2 бита.

   1         lui     $t8 0xffff              # база MMIO
   2         move    $t7 $zero               # счётчик
   3         move    $t6 $zero               # предыдущее значение
   4 loop:
   5         move    $t1 $zero               # общий результат сканирования
   6         li      $t0 1                   # первый ряд
   7         sb      $t0 0x12($t8)           # сканируем
   8         lb      $t0 0x14($t8)           # забираем результат
   9         or      $t1 $t1 $t0             # добавляем биты в общий результат
  10         li      $t0 2                   # второй ряд
  11         sb      $t0 0x12($t8)
  12         lb      $t0 0x14($t8)
  13         or      $t1 $t1 $t0
  14         li      $t0 4                   # третий ряд
  15         sb      $t0 0x12($t8)
  16         lb      $t0 0x14($t8)
  17         or      $t1 $t1 $t0
  18         li      $t0 8                   # четвёртый ряд
  19         sb      $t0 0x12($t8)
  20         lb      $t0 0x14($t8)
  21         or      $t1 $t1 $t0
  22         beq     $t1 $t6 same
  23         sb      $t1 0x10($t8)           # запишем результат в биты окошка
  24         move    $a0 $t1                 # выведем результат как двоичное
  25         li      $v0 35
  26         syscall
  27         li      $a0 10
  28         li      $v0 11
  29         syscall
  30         addi    $t7 $t7 1               # счётчик
  31         sb      $t7 0x11($t8)           # запишем его в другое окошко
  32         move    $t6 $t1
  33 same:   ble     $t7 20 loop
  34 
  35         li      $v0 10
  36         syscall

Последовательность внутри цикла — команды запуска считывания соответствующего ряда и аккумуляции считанных значений. Программа выполняет поллинг, но без заполнения промежутков между опросами устройства «бессмысленным ожиданием»: после каждого опроса немедленно начинается следующий. На практике такие программы начинают потреблять чень много процессорного времени, не делая почти ничего (пользователь — самое медленное на свете устройство ввода ☺). Чтобы ожидание меньше нагружало процессор, можно использовать системный вызов sleep (32), которрый передаёт управление операционной системе на указанный в миллисекундах период. Может, хоть ОС в это время будет делать что-то полезное?

Графический дисплей

Графический дисплей Mars (Bitmap Display) представляет собой воображаемое внешнее устройство Mars, состоящее из единственной области памяти, целиком отображённой с помощью MMIO в адресное пространство Mars (по умолчанию — на начало статических данных 0x10010000, но есть и другие варианты).

BitmapDisplay.png

Запись машинного слова 0x00RRGGBB по адресу Base Address + Offset приведёт к появлению на экране точки цвета #RRGGB в цветовом пространстве RGB. Подробнее про цветовой пространство RGB можно прочитать в Википедии, там же есть ссылка на таблицу цветов HTML.

Ширина и высота экрана задаются в точках на экране компьютера. Один пиксель Bitmap-устройства (unit) представляет собой прямоугольник из точек экрана. Если он равен одной точке (Unit Width × Unit Length — это 1×1), количество пикселей в видеопамяти устройства совпадает с количеством пикселей в соответствующей области экрана. Если размеры пикселя увеличивать (не забываем нажать кнопку «Reset»), он превратится в видимый прямоугольник, а общий объём видеопамяти пропорционально сократится.

Объём потребляемой памяти определяется так (*4 — потому что один пиксель задаётся машинным словом длиной в 4 байта):

  • ALL = DisplayWidth * DisplayHeight * 4 / (UnitWidth * UnitHeight)

Координаты точки со смещением Offest вычисляются так

  • X = (Offset / 4) % (DisplayWidth / UnitWidth)

  • Y = (Offset / 4) / (DisplayWidth / UnitWidth)

Обратно, смещение точки с координатами X,Y вычисляется так:

  • Offset = Y*DisplayWidth*4/UnitWidth+X*4

Пример: классическая программа, рисующая «звёздное небо» (точки случайного цвета по случайным координатам). В этой программе координаты не разделяются на X и Y, потому что они всё равно случайные, вместо этого берётся случайное число в диапазоне 0…512*256

   1 .eqv    ALLSIZE 0x20000                 # размер экрана в ячейках
   2 .eqv    BASE    0x10010000              # MMIO экрана
   3 .text
   4 again:  move    $a0 $zero
   5         li      $a1 ALLSIZE             # Максимальное 512*Y+X + 1
   6         li      $v0 42
   7         syscall                         # Случайное 512*Y+X
   8         sll     $t2 $a0 2               # Домножаем на 4
   9         move    $a0 $zero
  10         li      $a1 0x1000000           # Максимальный RGB-цвет + 1
  11         li      $v0 42
  12         syscall                         # Случайный цвет
  13         sw      $a0 BASE($t2)
  14         j       again

Пример: случайные отрезки

Дисплей можно «привязать» к нескольким различным местам «настоящей» памяти (т. е. сделать видеопамять доступной посредством MMIO).

Зададим константами размеры дисплея и базовый адрес

   1 .eqv    BASE 0x10010000
   2 .eqv    WIDTH 512
   3 .eqv    HEIGHT 256

Поскольку стандартное для MIPS место размещения локальных данных занято MMIO, совместим их с областью глобальных данных (она практически не используется в однозадачном режиме). Зададим в этой области три переменных: текущие координаты на экране («курсор») и текущий цвет графических операций. Эти координаты и цвет будут использоваться всякий раз вместо того чтобы задавать их явно, например, а качестве стартовой точки при рисовании фигур, а устанавливать мы их будем в финальную точку фигуры.

   1 .data   0x10008000
   2 X:      .half 0
   3 Y:      .half 0
   4 Color:  .word 0

Напишем подпрограмму рисования точки по заданным координатам. Точка будет рисоваться текущим цветом, а заданные координаты — сохраняться.

   1 .text
   2 dot:    # $a0=x $a1=y
   3         sh      $a0 X
   4         sh      $a1 Y
   5         mul     $a1 $a1 WIDTH
   6         add     $a0 $a0 $a1
   7         sll     $a0 $a0 2
   8         lw      $a1 Color
   9         sw      $a1 BASE($a0)
  10         jr      $ra

Для написания более сложных функций подготовим несколько макросов: push и pop; пролог subroutine и эпилог return для написания универсальных подпрограмм согласно конвенции, а также «обёртку» hrandom вокруг системного вызова Mars для генерации случайного числа заданного диапазона размером в полуслово:

   1 .macro  push    %r
   2         addi    $sp $sp -4
   3         sw      %r ($sp)
   4 .end_macro
   5 
   6 .macro  pop     %r
   7         lw      %r ($sp)
   8         addi    $sp $sp 4
   9 .end_macro
  10 
  11 .macro  subroutine
  12         push    $ra
  13         push    $s0
  14         push    $s1
  15         push    $s2
  16         push    $s3
  17         push    $s4
  18         push    $s5
  19         push    $s6
  20         push    $s7
  21         push    $fp
  22         move    $fp $sp
  23 .end_macro
  24 
  25 .macro  return
  26         move    $sp $fp
  27         pop     $fp
  28         pop     $s7
  29         pop     $s6
  30         pop     $s5
  31         pop     $s4
  32         pop     $s3
  33         pop     $s2
  34         pop     $s1
  35         pop     $s0
  36         pop     $ra
  37         jr      $ra
  38 .end_macro
  39 
  40 .macro  hrandom %range %var
  41         li      $a0 0
  42         li      $a1 %range
  43         li      $v0 42
  44         syscall
  45         sh      $a0 %var
  46 .end_macro

Первый параметр системного вызова Mars № 42 — т. н. «номер случайной последовательности», достаточно, чтобы он был равен 0. Напишем подпрограмму рисования отрезка из текущей точки в заданную:

   1         # нарисовать линию от предыдущей точки
   2         # $a0=x1 $a1=y1
   3 lineto: subroutine
   4         lh      $s0 X           # X0
   5         lh      $s1 Y           # Y0
   6         move    $s2 $a0         # X1
   7         move    $s3 $a1         # Y1
   8         sub     $s4 $s2 $s0     # W
   9         abs     $t0 $s4
  10         sub     $s5 $s3 $s1     # H
  11         abs     $t1 $s5
  12         move    $s6 $t0         # количество точек N
  13         bge     $t0 $t1 xmax
  14         move    $s6 $t1         # это N больше
  15 xmax:   move    $s7 $zero       # шаг i
  16 loop:   bgt     $s7 $s6 done    # Нарисовали X1:Y1?
  17         # X=X0+W*i/N
  18         mul     $t0 $s4 $s7
  19         div     $t0 $s6
  20         mflo    $t0
  21         add     $a0 $t0 $s0     # новый X
  22         # Y=Y0+H*i/N
  23         mul     $t2 $s5 $s7
  24         div     $t2 $s6
  25         mflo    $t2
  26         add     $a1 $t2 $s1     # новый Y
  27         jal     dot             # поставим точку
  28         addi    $s7 $s7 1
  29         j       loop
  30 done:
  31         sh      $s2 X
  32         sh      $s3 Y
  33         return

Подпрограмма запоминает заданную конечную точку отрезка в качестве текущей. Получается нечто вроде «черепашьей графики».

Наконец, напишем программу, заполняющую дисплей отрезками случайного цвета(для того, чтобы случайный цвет в пространстве RGB оказался достаточно ярким, пришлось написать специальную подпрограмму):

   1         # Достаточно яркий случайный цвет
   2 randomcolor:
   3         li      $t0 0
   4 rcnext: li      $a0 0           # Цикл по B, G, R
   5         li      $a1 0x10
   6         li      $v0 42
   7         syscall
   8         sll     $a0 $a0 4       # каждый цвет поярче
   9         sb      $a0 Color($t0)
  10         addi    $t0 $t0 1
  11         blt     $t0 3 rcnext
  12         jr      $ra
  13 .data
  14 nx:     .half   0
  15 ny:     .half   0
  16 
  17 .text
  18 .globl  main
  19 main:
  20         hrandom WIDTH X
  21         hrandom HEIGHT Y
  22 
  23 forever:
  24         jal     randomcolor
  25 
  26         hrandom WIDTH nx
  27         hrandom HEIGHT ny
  28 
  29         move    $a1 $a0
  30         lh      $a0 nx
  31         jal     lineto
  32         j       forever

Программа начинается с метки main, так что при сборке надо включить «Initialize Program Counter to global 'main' if defined» в настройке Mars.

Lines.png

В силу природы MMIO прямая запись в видеопамять — операция долгая и ресурсоёмкая. Это видно, например, из того, с какой скоростью Mars может записывать слова в оперативную память, и с какой — последовательно заполнять точки графического дисплея. В то же время именно графические устройства требуют очень быстрой работы с очень большими объёмами данных

  • Устройство снабжается собственной памятью и DMA-интерфейсом, и вместо MMIO большие объёмы данных пересылаются по DMA
  • Устройство снабжается мощным процессором (GPU), весь обсчёт идёт на GPU, а на долю процессора остаётся только обработка пересылки данных по DMA, когда памяти графического устройства не хватает

Д/З

  • EJudge: EightSectors 'Восемь секторов'

    Написать программу, которая вводит 8 целых чисел (цвета в представлении MARS Bitmap Display) и заполняет ими Bitmap Display размером 128×128 точек с базой в 0x10010000 по следующему принципу (цифры обозначают номер введённого цвета, их рисовать не надо :) ):

    • EightSectors.png

    Прошу обратить внимание на углы: ld.png, lu.png, rd.png, ru.png и центр: c.png. Чтобы углы и центр были виднее, настройки Bitmap Display можно умножить на 4 (программа от этого не изменится).

    • Для проверки на EJudge необходимо вывести в столбик в шестнадцатеричном виде всю видеопамять
    Input:

    16711680
    65280
    255
    16776960
    16711935
    65535
    16777215
    8947848
    Output:

    0x00ff0000
    0x00ff0000
    0x00ff0000
    0x00ff0000
    (много строк)
    0x00ffff00
    0x00ffff00
    0x00ffff00
    0x00888888

LecturesCMC/ArchitectureAssembler2019/08_TrapsAndMMIO (последним исправлял пользователь FrBrGeorge 2019-12-10 20:02:38)