Skip to content

Latest commit

 

History

History
293 lines (187 loc) · 18.3 KB

lab5.textile

File metadata and controls

293 lines (187 loc) · 18.3 KB

ЛАБОРАТОРНАЯ РАБОТА 5

Цель: Изучение приемов преобразования числовых данных.

Краткие теоретические сведения

Инструкции умножения

В системе команд ARM реализованы две инструкции для умножения чисел: умножение (инструкция MUL) и умножение с накоплением (инструкция MLA).

Формат команд, соответствующих этим инструкциям, приведен на рис. 1.

Как и в случае других команд, поле условия определяет, в каком случае команда должна быть выполнена (он может использоваться, чтобы пропустить команду при определенном стечении обстоятельств, как более простая альтернатива связке команд сравнения и условного перехода).

Обе команды для выполнения целочисленного умножения используют алгоритм, основанный на цепочке операций логического сдвига и сложения 8-битных операндов (Booth’s algorithm).

Формат соответствующих инструкций ассемблера выглядит следующим образом:


MUL{cond}{S} Rd, Rm, Rs
MLA{cond}{S} Rd, Rm, Rs, Rn

Здесь {cond} – это двухсимвольная мнемоника условия выполнения команды, а {S} – суффикс, присутствие которого разрешает команде воздействовать на флаги регистра CPSR, а поля Rd, Rm, Rs и Rn – выражения, которые определяют номер регистра общего назначения (кроме регистра R15).

Приведем примеры:


MUL R1,R2,R3 @ R1 = R2*R3_
MLAEQS R1,R2,R3,R4 @ По условию EQ выполнить R1 = R2*R3 + R4
@ и разрешить воздействие на флаги CPSR выполнения (S)

Как видно, действие команды MUL можно описать формулой Rd = Rm · Rs. Четвертый аргумент (то есть Rn) командой игнорируется, а в машинном коде соответствующее поле должно быть равно нулю с целью совместимости (на случай, если когда-нибудь система команд будет дополнена).

Действие команды умножения с накоплением MLA описывается формулой Rd = Rm · Rs + Rn. Заметим, что при Rm=1 или Rs=1 эта команда становится эквивалентна выполнению команды ADD.

На операнды инструкций умножения есть два ограничения:

  1. Нельзя использовать в качестве операндов регистр-результат Rd одновременно в качестве регистра-операнда (Rm, Rn или Rs).
  2. Нельзя использовать регистр R15 в качестве регистра-операнда или регистра-результата.

Все другие комбинации остальных регистров будут давать корректный результат. Если требуется, в роли Rd, Rn и Rs может выступать один и тот же регистр.

Знаковые и беззнаковые операции

Обе команды позволяют выполнять операции только с целочисленными операндами, как без знака так и со знаком (дополнение до двух). Теоретически результат умножения двух 32-битных целых чисел может занимать 64 бита; однако в процессорах ARM размер аргументов должен быть одинаков и равен машинному слову, т. е. 4 байтам. Поэтому команды MUL и MLA сохраняют в регистре-приемнике только младшие 32 бита результата.

Старшие 32 бита результата отбрасываются, а поскольку результаты умножения знаковых и беззнаковых 32-битных операндов различаются только своими старшими 32 битами, результат выполнения этих команд будет одинаков как для операндов со знаком, так без знака. Проиллюстрируем сказанное на двух примерах:

Пример интерпретации операндов как знаковых:

Пусть, операнд А = -10, а операнд B = 20. Результатом их умножения будет число -200, которое корректно записывается как 0xFF FF FF 38.

Пример интерпретации операндов как беззнаковых:

Пусть, операнд А = 4 294 967 286, а операнд B = 20. Результатом их умножения будет число 85 899 345 720, которое корректно записывается как 0x13 FF FF FF 38. Но, поскольку старшие 32 бита результата отбрасываются, окончательным результатом умножения будет все то же число 0xFF FF FF 38.

Флаги регистра CPSR

Как было сказано, возможность воздействовать на флаги регистра CPSR определяется битом S в соответствующем поле команды. Флаги отрицателььного результата N и флаг нуля Z устанавливаются в соответствии с результатом умножения: флаг N становится равным 31-му биту результата, а флаг Z устанавливается, если результат оказывается нулевым. Флаг переноса С устанавливается в неизвестное состояние, а флаг переполнения V не используется.

Число машинных тактов при выполнении

Команда MUL выполняется за 1S + mI машинных тактов, а команда MLA – за 1S + I(m + 1) машинных тактов, где S и I зависят от типа машинных тактов, а m – количество 8-битных множителей, необходимых для выполнения умножения, зависит от содержимого операнда-множителя Rs. Возможные значения m перечислены ниже:

  • m = 1, если биты [31:8] операнда-множителя – либо все нули, либо все единицы;
  • m = 2, если биты [31:16] операнда-множителя – либо все нули, либо все единицы;
  • m = 3, если биты [31:24] операнда-множителя – либо все нули, либо все единицы;
  • m = 4 во всех остальных случаях.

Данные в оперативной памяти

Рассмотрим подробнее, как с помощью скрипта компоновщика разместить секцию .data программы в ОЗУ. В качестве примера такой программы возьмем программу сложения чисел, которая загружает два значения из оперативной памяти, складывает их и сохраняет результат обратно в ОЗУ. Разместим исходные значения и место для сохранения результата в секции .data.


.data
val1: .4byte 10 @ Первое число
val2: .4byte 30 @ Второе число
result: .4byte 0 @ Место 4 байта для результата

.text
.align
start:
ldr r0, =val1 @ r0 = &val1
ldr r1, =val2 @ r1 = &val2

ldr r2, [r0] @ r2 = *r0
ldr r3, [r1] @ r3 = *r1

add r4, r2, r3 @ r4 = r2 + r3

ldr r0, =result @ r0 = &result
str r4, [r0] @ *r0 = r4
stop: b stop

Для линковки программы воспользуемся следующим скриптом компоновщика:


SECTIONS {
. = 0×00000000;
.text : { * (.text); }
. = 0xA0000000;
.data : { * (.data); }
}

Дамп таблицы символов ELF-файла, созданного этим скриптом, должен выглядеть так:


$ arm-none-gnueabi-nm -n add-mem.elf

00000000 t start
0000001c t stop
a0000000 d val1
a0000001 d val2
a0000002 d result

Достоинством созданной программы является ее понятность и простота. Однако ее нельзя назвать полностью работоспособной по той причине, что ОЗУ – энергозависимая память, и сразу после включения питания микропроцессорного устройства данным в оперативной памяти просто неоткуда взяться.

Поэтому изначально весь код и все исходные данные должны быть сохранены во флеш-памяти. При включении питания стартовый код должен скопировать данные из флеш-памяти в оперативную память, и только затем приступать к исполнению программы. Таким образом секция .data нашей программы имеет два адреса: загрузочный адрес из флеш-памяти и адрес в оперативной памяти во время выполнения (т. н. VMA или виртуальный адрес памяти).

Чтобы программа была полностью работоспособной, в нее нужно внести два изменения:

  1. Скрипт нужно преобразовать так, чтобы он указывал для секции .data и загрузочный, и виртуальный адреса памяти.
  2. Необходим фрагмент кода, который скопирует секцию .data из флеш-памяти в ОЗУ.

Указание загрузочного адреса

Виртуальный адрес должен использоваться компоновщиком для определения адресов меток. В предыдущем скрипте для .data был определён именно виртуальный адрес. Если загрузочный адрес явно не указан, по умолчанию он равен виртуальному. Это нормально, когда программа полностью выполняется с флеш-памяти, но если во время выполнения программы данные должны быть расположены в ОЗУ, эти адреса должны различаться.

Задать конкретное значение загрузочного адреса можно ключевым словом AT:


SECTIONS {
. = 0×00000000;
.text : { * (.text); }
etext = .; ❶

. = 0xA0000000;
.data : AT (etext) { * (.data); } ❷
}

Внесенные в скрипт изменения помечены цифровыми метками. Рассмотрим их подробнее.

  1. etext присваивается значение счётчика адреса в данной позиции. В нашем случае etext содержит адрес следующего свободного поля после всего кода, находящегося во флеш-памяти. Позже это значение пригодится при определении, куда во флеш-памяти поместить секцию .data. Отметим, что сам по себе etext не выделяет память, а просто делает запись в таблице символов.
  2. Ключевое слово AT (точнее, его аргумент) определяет загрузочный адрес секции .data. В нашем случае загрузочный адрес секции .data определяется как место, расположенное сразу после кода во флеш-памяти.

Копирование секции .data в ОЗУ

Для копирования данных из флеш-памяти в ОЗУ необходимо знать три числа:

  1. Адрес данных во флеш-памяти (flash_sdata)
  2. Адрес данных в ОЗУ (ram_sdata)
  3. Размер секции .data (data_size)

Используя эту информацию, мы можем копировать данные, используя следующий фрагмент кода:


ldr r0, =flash_sdata
ldr r1, =ram_sdata
ldr r2, =data_size

copy:
ldrb r4, [r0], #1
strb r4, [r1], #1
subs r2, r2, #1
bne copy

Задать необходимые значения можно непосредственно через скрипт:


SECTIONS {
. = 0×00000000;
.text : {
* (.text);
}

flash_sdata = .; ❶
. = 0xA0000000;
ram_sdata = .; ❷
.data : AT (flash_sdata) {
* (.data);
}
ram_edata = .; ❸
data_size = ram_edata – ram_sdata; ❹
}

Как видно, содержимое скрипта на этот раз помечено четырьмя метками:

(1) Начало данных во флеш-памяти, следующих сразу за кодом
(2) Начало данных в ОЗУ, т. е. базовый адрес оперативной памяти
(3), (4) Размер данных вычисляется путём вычитания начального и конечного адресов данных в ОЗУ. Как мы видим, в скриптах разрешено использовать простые арифметические выражения.

Итоговая программа должна выглядеть следующим образом:


.data
val1: .4byte 10 @ Первое число
val2: .4byte 30 @ Второе число
result: .space 4 @ 1 байт для результата
.text

start:
ldr r0, =flash_sdata
ldr r1, =ram_sdata
ldr r2, =data_size

copy:
ldrb r4, [r0], #1
strb r4, [r1], #1
subs r2, r2, #1
bne copy

ldr r0, =val1 @ r0 = &val1
ldr r1, =val2 @ r1 = &val2

ldr r2, [r0] @ r2 = *r0
ldr r3, [r1] @ r3 = *r1

add r4, r2, r3 @ r4 = r2 + r3

ldr r0, =result @ r0 = &result
str r4, [r0] @ *r0 = r4
stop: b stop

надо хотя бы одним предложением рассказать, что делает директива .space – ведь ее раньше не было

Просмотр оперативной памяти в QEMU

Для просмотра ячейки ОЗУ по ее физическому адресу в мониторе QEMU предусмотрена следующая команда:


xp /format address

Параметр format позволяет указать, в каком формате должны быть показаны данные, и имеет следующую структуру:


/[count][data_format][size]

Назначение полей формата:

  • count: число элементов для вывода;
  • data_format: x – шестнадцатиричный, d – десятичный, u – беззнаковый десятичный, o – восьмеричный, c – символьный, i – дизасемблированная инструкция процессора;
  • size: b – 8 бит, h – 16 бит, w – 32 бит, g – 64 бит.

Параметр address может быть либо значением прямого адреса, например, 0×20000, либо именем регистра, содержащего нужный адрес.

Покажем использование команды xp на примере программы, скомпилированной с помощью последнего приведенного скрипта. Выполнение программы и общение с монитором QEMU будет выглядеть следующим образом:


$ qemu-system-arm -M connex -pflash flash.bin -nographic -serial /dev/null

(qemu) xp /4dw 0xA0000000
a0000000: 10 30 40 0

Задание для выполнения

  1. Написать программу, реализующую функции простейшего калькулятора для работы с двумя целыми положительными числами в диапазоне 0..255:
    1. Операции: +, -, *, /;
    2. Программа работает циклически до ввода пустой строки;
    3. При вводе недопустимого значения оно игнорируется с выдачей диагностического сообщения.