Опубликован: 19.01.2025 | Доступ: свободный | Студентов: 0 / 0 | Длительность: 05:57:00
Лекция 7:

Лабораторная работа №6. Адресация элементов массива, организация цикла

< Лекция 6 || Лекция 7 || Лекция 8 >

6.1 Цель и задачи

Целью работы является разработка программы преобразования данных для приобретения практических навыков программирования на языке ассемблера и закрепления знаний по работе с массивами и циклами. Задачи:

  1. Изучить применение методов адресации для манипуляции массивами.
  2. Освоить подходы к организации циклов средствами условных и безусловных переходов.

Презентация к блоку "Ассемблер RISC-V"

6.2. Основные теоретические сведения

6.2.1 Хранение массивов в памяти

Архитектура RISC-V обычно использует прямой порядок байтов (от младшего к старшему; little-endian). RISC-V применяет память с побайтовой адресацией. Это значит, что каждый байт памяти имеет уникальный адрес. Поскольку 32-битное слово состоит из четырех 8-битных байтов, то адрес каждого слова (word address) кратен 4. Старший байт (most significant byte, MSB) находится слева, а самый младший байт (least significant byte, LSB) - справа.

Для обращения к элементу одномерного массива в команде используется формула для вычисления его адреса в памяти.

Адрес эл-та = (адрес нач. массива) + (№эл-та) ? (размер эл-та в байтах)

Расчет адресов элементов массива

Рис. 6.1. Расчет адресов элементов массива

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

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

la rs1, mas  # Загрузить адрес массива
lw rd, 0(rs1)#Адрес элемента = Смещение (0) + База (rs1)

Пояснение к примеру:

  • Смещение - адрес элемента внутри массива
  • База - базовый адрес массива в сегменте данных

Другая форма:

li rs1,0
lw rd, mas (rs1) #  Адрес элемента = База (mas) +Смещение (rs1)

6.2.2 Организация циклов

В языках высокого уровня циклы предназначены для многократного выполнения участка кода в зависимости от условия. В Ассемблере RISC-V нет специальных инструкций для организации цикла. Для перебора элементов массива в цикле требуется продумать самостоятельно вид цикла, регистр-счетчик количества повторений цикла, условие окончания, регистр для хранения флага окончания и временного индикатора для сравнения с индикатором. Проверка условий выполняется операторами "если-то-иначе", организованными командами условного перехода (beq, bne, blt, bge,bgeu, bltu) или безусловного перехода (jal, jalr и псевдокоманды j, jr ).

Команды условного перехода имеют формат типа B.

Структура команд типа B

Рис. 6.2. Структура команд типа B

В таблице 6.1 представлены все команды условного перехода

Таблица 6.1. Команды условного перехода
Instruction name opcode funct3 description
beq Branch == 1100011 0x0 if (rs1 == rs2) pc+= imm
bne Branch != 1100011 0x1 if (rs1 != rs2) pc+= imm
blt Branch < 1100011 0x4 if (rs1 < rs2) pc+= imm
bge Branch >= 1100011 0x5 if (rs1 >= rs2) pc+= imm
bltu Branch < Un 1100011 0x6 if (rs1 < rs2) pc+= imm
bgeu Branch >= Un 1100011 0x7 if (rs1 >= rs2) pc+= imm

Команда условного перехода сравнивает значения регистров rs1 и rs2. Если результат сравнения (условие) является истинным, программный счетчик увеличивается на imm. Например, команда blt rs1, rs2, imm проверяет, меньше ли содержимое rs1, чем содержимое rs2. Если это так, то pc = pc + imm. В противном случае работа продолжается со следующей инструкцией в памяти (pc = pc +4).

Инструкции blt и bge используют значения со знаком; а bgeu этого не делает. Использование условного перехода bge и beq

bge s2, s3, else          	# if (a < b):  c = a + 1
addi s4, s2, 1               	 
beq zero, zero, end
else:                         # else:       	c = b + 2
 addi s4, s3, 2               	 
end:                          # s2=a; s3=b; s4=c;

Использование условного перехода blt

Таблица 6.2. Пример использования условного перехода blt
Адрес Машинный код Инструкция Комментарий
0x00 0xffb00093 addi s2, zero, -5 s2 = -5
0x04 0x00500113 addi s3, zero, 5 s3 = 5
0x08 0x0020c463 blt s2, s3, 0x8 if (s2 < s3) pc = pc+ 8
0x0c 0x00100193 addi s4, zero, 1 skipped if (s2 <s3): s4 = 1
0x10 0x00200193 addi s4, zero, 2 s4 = 2

Инструкция по адресу 0x08 проверяет, является ли s2 < s3, то есть -5 < 5. Поскольку это верно, программный счетчик устанавливается на адрес 0x10, а инструкция по адресу 0x0c пропускается.

Если бы команда перехода по адресу 0x08 была bltu, отрицательное значение регистра s2 рассматривалось бы как положительное значение (0xfffffffb как положительное шестнадцатеричное значение для 32-разрядной версии), и результат сравнения был бы ложным.

Ниже приведен более сложный пример программы подсчета количества нулевых элементов в одномерном массиве.

.data
mas: .word -1 -2 -3 -4 1 2 0 4 2000 4096 65535 0 
size: .word 12 # длина массива
head: .asciiz "\n Null element amount: "

.text
     la t0, mas        # загрузить адрес массива - начальный индекс [i]
     lw t5, size       # загрузить длину массива в регистр
loop:
     lw t3, 0(t0)     	# поместить значение mas[i] в регистр $t3
     bne 0, t3, met1   	# перейти к met1 если $t3 не '0' и элемент массива не 'zero'
     addi t4, t4, 1    # увеличить счетчик нулевых элементов
met1: addi t5, t5, -1  # уменьшить счетчик цикла
     addi t0, t0, 4    # увеличить индекс текущего элемента
     bge t5, loop      # повторить, пока не закончили
     la a0, head       # загрузить адрес константы заголовка для печати
     li v0, 4                      
     ecall             # напечатать заголовок
     add a0, t4, zero  # загрузить значение счетчика нулевых элементов для печати
     li v0, 1                      	
     ecall             # напечатать значение счетчика

Согласно вышеприведенной программе в памяти последовательно расположены 12 четырёхбайтовых элемента массива mas (среди которых есть два нулевых), затем переменная size, содержащая размер массива (12), затем ASCII-коды символов выводимого сообщения head. Далее следуют три инструкции загрузки:

  1. адреса массива,
  2. адреса переменной-размера массива,
  3. значения переменной-размера массива.

Затем начинается цикл просмотра элементов (метка loop), в котором

  • загружается очередной элемент массива mas[i],
  • сравнивается с нулём и при необходимости увеличивается счетчик нулей,
  • уменьшается счетчик цикла - количество оставшихся итераций цикла,
  • увеличивается адрес элемента на его длину (4 Байта),
  • проверяется условие окончания цикла (счетчик цикла равен нулю).

Далее выполняются два системных вызова

  • для вывода строки-сообщения,
  • для вывода переменной - счетчика нулей.

В ряде случаев полезными выступают команды безусловного перехода, являющиеся аналогами инструкции GOTO. Команды безусловного перехода имеют формат типа J или I. Для RISC-V есть две инструкции безусловного перехода (Таблица 6.3).

Таблица 6.3. Команды безусловного перехода
Instruction name format opcode funct3 description
jal Jump and Link 1101111 - rd=PC+4; PC+=imm
jalr Jump and Link Register I 1101111 0x0 rd=PC+4; PC=rs1+imm

Команда jal rd, imm добавляет непосредственное значение к программному счетчику pc +=imm и записывает в регистр rd адрес после команды jal. Команда jalr rd, rs, imm устанавливает программный счетчик как результат pc = rs + imm, и записывает в rd обратный адрес. Обратный адрес - это инструкция по адресу, указанному после инструкции перехода.

Таблица 1.1. Тестовая таблица
Адрес Машинный код Инструкция Комментарий
0x00 0x00c000ef jal s2,0xc pc = 0x0 + 0xc= 0xc, s2 = pc + 4 = 0x4
0x04 0x00000013 addi zero, zero, 0 no operation
0x08 0x00010093 addi s2, s3, 0 s2= s3
0x0c 0x00008167 jalr s3, s2, 0 pc = s2, s3= pc + 4 = 0x10
0x10 0x00100093 addi s2, zero, 1 s2= 1

Последовательность выполнения программы выглядит следующим образом. Программа начинается с команды перехода jal s2, 0xc по адресу 0х00. Эта команда записывает обратный адрес в регистр2 и переходит к 0х0c. В 0х0c команда перехода jalr s3, s2, 0 записывает свой соответствующий обратный адрес (0х10)в регистр s3 и выполняет переход (возвращает) к адресу, сохраненному в регистре s2, который равен 0х04. Программа продолжает выполнение инструкции в 0х04, которая ничего не делает. Следующая команда в 0х08 перемещает значение 0х10 (обратный адрес jalr) в регистр s2. После этого второй проход jalr в 0xc переходит к 0х10, значение регистра s2. Последовательность счетчика команд 0х00, 0х0c, 0х04, 0х08, 0х0c, и 0х10.

6.3. Задание к лабораторной работе

  1. Создать программу согласно варианту, которая
    • выполняет заданные действия,
    • выводит на экран сообщения о результатах/ошибках.
  2. Откомпилировать программу и запустить на исполнение
  3. Отладить программу, проследить изменения в регистрах и оперативной памяти.

6.3.1 Описание последовательности выполнения работы

Скомпилируйте исходную программу и убедитесь в ее работоспособности в RISC-V ОС, либо через qemu-riscv64.

6.3.2 Пример выполнения задания на защиту

Напишите программу сложения целых чисел от 0 до 100:

  • t0 используйте для текущего индекса quot;iquot; для цикла
  • t1 хранит величину для сравнения с текущим индексом (в данном примере 100)
  • t2 хранит текущую сумму (по окончанию программы - результат)

Код решения:

andi t0, t0, 0 # Clearing contents of register t0
andi t1, t1, 0 # Clearing contents of register t1
andi t2, t2, 0 # Clearing contents of register t2
# Functions as variable "sum"
li t1, 100 # Load t1 with value 100
loop: add t2, t2, t0 # Sum = Sum+i
addi t0, t0, 1 # Increment index "i"
blt t0, t1, loop # Iterate if t0<t1
End: j End # End of WHILE loop

ВАРИАНТЫ ЗАДАНИЙ

  1. Найти минимальный и максимальный элемент массива А и запомнить их номера и адреса в переменных в памяти A_min, A_max. Вывести переменные, и предусмотреть случай (вывести сообщение), когда имеются все/несколько минимальных или максимальных элементов.
  2. Для заданного массива А посчитать и запомнить количество нулевых/отрицательных/положительных элементов. Вывести сообщения с этими значениями и предусмотреть случай (вывести сообщение), когда какие-то типы элементов отсутствуют.
  3. Найти элементы массива А, содержащие 2 единицы в двоичном представлении и запомнить их номера и адреса в новых одномерных массивах B_nom и B_adr, предусмотреть случай, когда таких элементов нет. Вывести сообщение о количестве таких элементов или об их отсутствии.
  4. Для заданного массива А составить новый массив В, каждый элемент которого В[i] соответствует № первого слева (старшего) нулевого разряда элемента А[i] . Предусмотреть случай отсутствия нулевых разрядов элемента. Вывести сообщение с числом таких элементов или штатном режиме - весь массив В заполнен.
  5. Для заданного массива А составить новый массив В, каждый элемент которого В[i] соответствует № первого справа (младшего) единичного разряда элемента А[i]. Предусмотреть случай отсутствия единичных разрядов элемента. Вывести сообщение с числом таких элементов или штатном режиме - весь массив В заполнен.
  6. Для заданного массива А составить новый массив В, каждый элемент которого В[i] равен сумме единиц в двоичном представлении элемента А[i]. Посчитать общее количество единиц. Предусмотреть случай отсутствия единичных разрядов элементов. Вывести сообщение с общим количеством единиц или об их отсутствии.
  7. Вычислить сумму всех отрицательных, положительных элементов массива А, число нулевых элементов, число четных и нечетных элементов и запомнить эти 5 значений в указанном порядке в одномерном массиве В. Вывести сообщения о полученных результатах, предусмотреть случай, когда некоторые виды элементов отсутствуют.
  8. Вычислить сумму всех нулевых и единичных битов в двоичном представлении элементов массива А, запомнить эти значения в памяти и вывести эти значения или сообщение об отсутствии некоторых видов битов.
  9. Для заданного массива А рассчитать разницу между максимальным и минимальным элементом и вывести это число. Предусмотреть случай их отсутствия (когда все элементы массива равны) и вывести об этом сообщение.
  10. Вычислить сумму всех элементов массива А, больше заданной переменной. Вывести сообщения о полученных результатах, предусмотреть случай, когда такие элементы отсутствуют.
  11. Для заданного массива А составить новый одномерный массив символьных строк В. Первым символом строки В[i] будет символ "Р" (positive), если элемент А[i] положительный и "N", если отрицательный. Вторым символом строки В[i] будет "-", третьим символом строки В[i] будет "Z", если элемент А[i] нулевой. Вывести сообщения об отсутствии одного из типов элементов и числе положительных элементов.

6.4. Вопросы для контроля

  1. У нас есть массив int (32 бита) с именем arr 0. Указатель на первый элемент arr0, хранится в регистре a1. Ответьте на следующие вопросы.
    • a. Как поместить четвертый элемент arr 0 в регистр t1?
    • b. Как рассчитать t1 + 16? Сохраните результат в регистре t2
    • c. Найдите эффективный способ вычисления t2 /16 и t2 %16. Сохраните результаты в t3 и t4 соответственно. Обратите внимание, что / - это целочисленное деление, а % - операция по модулю. (подсказка: использование сдвига и логических операций)
  2. У нас есть массив int (32 бита) с именем arr1. Указатель на первый элемент arr1 хранится в регистре a2. У нас также есть регистры
    t1 = 0xAAAAAAAA, t2 = 0xFEDCBA98
    

    Ответьте на следующие вопросы:

    • a. Каково значение t3 для следующей последовательности инструкций?
      slli t3, t1, 4
      srli t3, t3, 4
      
    • b. Каково значение t3 для следующей последовательности инструкций?
      slli t3, t2, 3
      srai t3, t3, 3
      
    • c. Напишите фрагмент ассемблерной программы:
      • Сохранить результат t1 & t2 в регистре t4;
      • Сохранить t4 в первом элементе arr1;
      • Сохраните младшие 8 бит из t4 во втором элементе arr1.
  3. Рассмотрите следующие инструкции RISC-V
    li t1, 0
    li t2, 1
    li t3, 1
    li t4, 10
    LOOP:
    beq t1, t4, DONE
    add t5, t2, t3
    addi t2, t3, 0
    addi t3, t5, 0
    addi t1, t1, 1
    jal x0, LOOP
    DONE:
    # end of the program
    
    • a. Сколько раз выполняется цикл (между LOOP и DONE)?
    • b. Перечислите значение t2 на каждой итерации цикла.
    • c. Перечислите значение t3 на каждой итерации цикла.
    • d. Что делает эта программа?
< Лекция 6 || Лекция 7 || Лекция 8 >