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

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

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

С другой стороны, это очень эффективный механизм обращения к ОС почти (или совсем) без дополнительных действий, в отличие от 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:

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

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

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

Чего нет в Mars

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

Ввод-вывод

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

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

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

Задачи:

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

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

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), если клавиша активна (чтение)

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

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

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

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

snap-0414-103238.png

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

Дело в том, что это устройство спроектировано «как в жизни». Предполагается, что в клавиатуре есть всего 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. Кусок кода при этом может выглядеть так:
            li      $t0 1                   # первая строка
            sb      $t0 0xffff0012          # «подаём напряжение»
            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 бита.

Последовательность внутри цикла — команды запуска считывания соответствующего ряда и аккумуляции считанных значений. Программа выполняет поллинг, но без заполнения промежутков между опросами устройства «бессмысленным ожиданием»: после каждого опроса немедленно начинается следующий. На практике такие программы начинают потреблять чень много процессорного времени, не делая почти ничего (пользователь — самое медленное на свете устройство ввода ☺). Чтобы ожидание меньше нагружало процессор, можно использовать системный вызов 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 байта):

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

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

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

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