Процессор или центральный процессор - основное устройство любой электронно-вычислительной машины (ЭВМ) в широком понимании данного термина. Он осуществляет управление всеми элементами данной машины и выполняет основной набор вычислительных функций для конкретного решения. Упрощенный вариант процессора приведен на рисунке 7.1.
Блок управления осуществляет выборку инструкции из памяти ЭВМ, дает команды внутри процессора и управляет работой внешних устройств в том числе и памятью. Выбранная инструкция, если содержит данные в памяти, должна через дешифратор инструкций загрузить данные в соответствующие регистры процессора (условные регистр А и В). Дешифратор инструкций, если подразумевается какая-либо операция над данными, активирует соответствующий узел операционного блока. Результат инструкции может быть возвращен в память либо в один из регистров. Далее осуществляется выборка следующей инструкции или переход к другому участку памяти, в случае вызова перехода в результате исполнения инструкции.
(рис 7.1)
Архитектуру процессоров как широкого понятия можно определить набором правил, характеристик, определяющих структуру и функционирование данных устройств.
Первый вид классификации процессоров - система команд процессора (ISA - Instruction Set Architecture). ISA - набор инструкций, которые процессор может выполнять. Инструкции включает в себя как основные команды, так и формат данных, управление памятью и регистры. ISA является ключевым понятием в архитектуре микропроцессорных систем, так как определяет, каким образом программное обеспечение взаимодействует с аппаратным.
Выделяют три категории в данной классификации:
CISC (Complex Instruction Set Computer) сложный набор инструкций.
Такие процессоры могут иметь инструкции произвольной длины. Процессоры этой архитектуры способны выполнять несколько операций за несколько тактов. В архитектуре CISC обеспечивается большое количество методов адресации. В тоже время требуется условно небольшое количество регистров необходимых для реализации программы.
К данной категории процессоров относятся x86 совместимые процессоры.
RISC (Reduced Instruction Set Computer) упрощенный набор инструкций.
Процессоры RISC имеют одинаковый формат команд. Все команды имеют одинаковую длину. Инструкции исполняются за один такт за исключением сложных арифметических инструкций. RISC процессоры имеют небольшое количество команд по сравнению с CISC. Для повышения быстродействия имеют большое количество неименованных регистров, объединяемых в регистровый файл.
Для RISC процессоров признаками являются:
К данной категории процессоров относятся процессоры ARM, различные микроархитектуры микроконтроллеров, например, AVR, PIC, а также решения RISC-V.
VLIW (Very Long Instruction Word) - очень длинная машинная команда - архитектура процессоров, характеризующаяся возможностью объединения нескольких простых команд в так называемую связку. Входящие в нее команды должны быть независимы друг от друга и выполняться параллельно. Таким образом, из нескольких независимых машинных команд транслятор формирует одно очень длинное командное слово. Данная архитектура подразумевает, что длинная команда распараллеливается на несколько ядер, исполняющих каждое свою часть. При применении процессоров данной архитектуры большая часть работы выполняется компилятором языка программирования.
К данной категории процессоров относятся процессоры Эльбрус и некоторые решения IA64.
Вторым видом классификации архитектур процессоров является способ доступа к памяти. В данной классификации выделяют две категории:
1.Архитектура Фон Неймана:
Данная архитектура имеет следующие преимущества:
Недостатки:
На рисунке 7.2 приведена упрощенная схема организации данной архитектуры.
(рис 7.2)
Архитектура фон Неймана применяется x86 совместимыми процессорами и их последующими поколениями.
Гарвардская архитектура имеет следующие свойства:
Свойства данной архитектуры исключают недостатки архитектуры фон Неймана. Недостатком данной архитектуры является увеличенное количество линий связи с памятью и схемотехническая сложность интерфейса модулей памяти.
Процессоры с RISC системой команд строят по гарвардской архитектуре, для обеспечения максимальной производительности. На рисунке 7.3 приведена упрощенная схема организации гарвардской архитектуры.
(рис 7.3)
Другими специфическими особенностями классификации, скорее микроархитектур, можно отнести следующие:
По системам команд процессоры классифицируются на:
Еще одним видом классификации процессоров является степень параллелизма обработки данных:
1. SISD (Single Instruction, Single Data) - это архитектура, в которой один процессор выполняет одну инструкцию над одним набором данных в один момент времени. Это традиционная модель вычислений, используемая в большинстве обычных процессоров.
Каждый такт процессор выполняет одну инструкцию над одной частью данных, что ограничивает его производительность при обработке больших объемов данных.
Применение: подходит для простых задач и приложений, которые не требуют высокой степени параллелизма, таких как базовые вычисления и небольшие программы.
Преимущества: простота реализации и программирования.
Недостатки: низкая эффективность в сравнении с параллельными архитектурами при выполнении сложных задач.
2. SIMD (Single Instruction, Multiple Data) - это архитектура, в которой одна и та же инструкция применяется одновременно ко многим данным. Это позволяет значительно ускорить выполнение операций над большими массивами данных.
В один момент времени одна инструкция применяется ко всем элементам векторов или массивов. Например, сложение двух векторов чисел.
Применение: широко используется в обработке изображений, машинном обучении, научных вычислениях и т.д.
Преимущества: высокая производительность при обработке больших объемов однотипных данных, эффективное использование ресурсов.
Недостатки: ограниченная гибкость, поскольку не все задачи можно разбить на операции с одинаковыми данными.
3. MIMD (Multiple Instruction, Multiple Data) - это архитектура, в которой несколько процессоров могут выполнять разные инструкции над разными наборами данных одновременно. Это наиболее универсальный тип архитектуры.
Каждый процессор может выполнять свою собственную инструкцию и работать с собственными данными, что позволяет решать разнообразные задачи параллельно.
Применение: используется в многоядерных процессорах, кластерах, распределенных системах и для высокопроизводительных вычислений.
Преимущества: высокая степень гибкости и универсальности, возможность работы с разнообразными задачами.
Недостатки: более сложная реализация и необходимость в эффективной синхронизации и управлении потоками.
Кратко рассмотрим микроархитектуры процессоров, представленных на рынке.
Применяемые в большинстве решений вычислительных машин, процессоры реализованы на так называемой Intel совместимой архитектуре. Данные процессорные решения имеют разную микроархитектуру и совместимы между собой на уровне общих инструкций языка ассемблера. В то же время могут иметь собственные специфические инструкции, описываемые в документации разработчика. На рисунке 7.4 приведена общедоступная структурная схема процессора Intel Pentium [3], совместимость с которым у других производителей была наиболее близкой. На структурной схеме можно выделить следующие блоки:
(рис 7.4)
С точки зрения программиста в Intel архитектуре выделяются общие поименованные регистры, которые применяются при разработке программ на языке ассемблера. Данные регистры называют регистрами общего назначения (РОН). К некоторым из них можно обращаться секциями: байтом, словом двойным словом (для 32-разрядной x86 архитектуры). На рисунке 7.5 приведены поименованные 32-х разрядные регистры Intel x86 и форматы взаимодействия с частями данных регистров. При работе с 64-х разрядной архитектурой процессора префикс заменяется на букву R. Регистры общего назначения являются основой для разработки программ и позволяют решать основные задачи по обработке данных, таких, как хранение операндов для арифметических и логических операций, операндов вычисления адресов ячеек памяти, указателей на переменные в памяти.
Несмотря на общий характер данных регистров они всё же используются для определенных целей:
В 32-х разрядных процессорах архитектуры x86 для управления памятью применяются следующие сегментные регистры:
(рис 7.5)
Стек (стековая память) - структура данных в оперативной памяти, применяемая для временного хранения содержимого регистров процессора, например, для обработки подпрограммы прерывания. Данные упорядочиваются в стеке последовательно. Существуют следующие виды стеков:
Регистр указатель стека ESP фиксирует в себе адрес последних записанных данных.
Отдельно стоит отметить наличие регистра флагов, определяющего состояние процессора, применяемого для управления переходами.
Как видно из описания, x86 процессоры представляют собой сложную организацию с закрытой микроархитектурой. Механизмы управления и параллелизма представляют собой множество цепей управления, соответственно система управления является громоздкой и требует инженерного творчества для обеспечения высокой производительности.
Рассмотрим микроархитектуру RISC на примере восьмиразрядного микроконтроллера Atmega328P (устанавливается на все Arduino UNO отладочные платы). На рисунке 7.6 приведена упрощенная схема данного микроконтроллера со следующими основными блоками:
Расположенные справа модули представляют специфические для каждого микроконтроллера встроенные периферийные устройства, обеспечивающие сбор, прием/передачу данных и сигналов для исполнительных механизмов, т.е порты ввода-вывода данных (I/O Module).
Как видно из схемы память инструкций и данных разделены, соответственно архитектура организации памяти - гарвардская.
(рис 7.6)
ARM процессоры - это коммерческие лицензируемые ядра процессоров, построенные с использованием RISC команд, но с технологическими особенностями организации этого ядра. Компания ARM, разработчик различных инженерных решений, продает лицензии и документацию на ядро процессора конечным производителям микропроцессоров. Разработчики, используя свои периферийные модули и другие вычислительные блоки, добавляя их к ядру ARM, получают готовое решение. На рисунке 7.7 представлена упрощенная структурная схема процессора ARM9, реализованная на описаниях из разных информационных источников [4,5]. На структурной схеме можно выделить следующие блоки:
(рис 7.7)
Более подробная информация об организации работы процессоров, существующих архитектурах, методах работы с памятью приводится в литературе [1,2] и дополнительной литературе [2,3].
Для понимания различий в архитектуре рассмотренных процессоров реализуем решение задачи по вычислению чисел Фибоначчи на языках ассемблера (ISA). Предварительно приведем код для вычисления чисел Фибоначчи на языке Си (листинге 7.1).
include <stdio.h >
int main() {
int n= 10; // количество чисел Фибоначчи
unsigned long long fib[10]; // массив для хранения чисел Фибоначчи
// инициализация первых двух чисел Фибоначчи
fib[0] = 0;
fib[1] = 1;
// вычисление чисел Фибоначчи
for (int i = 2; i < n; i++) {
fib[i] = fib[i - 1] + fib[i - 2];
}
// вывод чисел Фибоначчи
printf("Первые %d чисел Фибоначчи:\n", n);
for (int i = 0; i < n; i++) {
printf("%llu ", fib[i]);
}
printf("\n");
return 0;
}
Для описанного выше примера приведем код на языке ассемблера процессора x86, реализованного в компиляторе NASM (листинге 7.2)
section .data
fmt db "Fibonacci(%d) = %d", 10, 0 ; Формат строки для вывода
n db 5; Измените здесь для получения n-го числа Фибоначчи
section .bss
result resd 1; Результат вычисления
section .text
extern printf; Импорт функции printf
global main; Точка входа в программу
main:
; Загружаем значение n
movzx ecx, byte [n]; Загружаем n в ecx
mov eax, 0; fib(0)
mov ebx, 1; fib(1)
cmp ecx, 0; Если n = 0
je .done; Переход к завершению
cmp ecx, 1; Если n = 1
je .next; Переход к следующему
; Итеративная часть
.loop:
;Вычисляем n-е число Фибоначчи
mov edx, eax ; Сохраняем предыдущую Fibonacci (fib(n-2))
add eax, ebx; fib(n) = fib(n-1) + fib(n-2)
mov ebx, edx; Обновляем fib(n-1) на fib(n-2)
dec ecx; Уменьшаем счетчик
jnz .loop; Повторяем до n = 0
.next:
; Задаем результат для вывода
mov [result], eax ; Сохраняем результат
.done:
; Вывод результата
push eax; Параметр: значение Fibonacci
push dword [n]; Параметр: n
push fmt; Строка для printf
call printf; Вызов функции printf
add esp, 12; Очистка стека
; Завершение программы
mov eax, 1; sys_exit
xor ebx, ebx; Устанавливаем код возврата 0
int 0x80; Вызов ядра
Приведем решение данной задачи на языке ассемблера для микроконтроллера Atmega328P. Код приведен в листинге 7.3.
.section .text .global fib fib: ; Сохранение регистров push r16 push r17 mov r16, r24 ; Инициализация значений ldi r18, 0; Заносим первое значение ldi r19, 1; Заносим второе значение ; Проверяем (0 или 1) cp r16, r18 breq .fib_done cp r16, r19 breq .fib_done ; Цикл вычислений ldi r20, 2 ldi r21, 0 .fib_loop: cp r20, r16 breq .fib_done ; Вычисления чисел Фибоначчи add r21, r18 mov r18, r19 mov r19, r21 ; Увеличение счетчика inc r20 ; Проверка окончания цикла .fib_loop brne .fib_loop ; Возврат по окончании вычислений call .fib_done .fib_done: mov r24, r19; Сохранение итога ; Завершение программы pop r17 pop r16 ret
Далее приводится код на ассемблере ARM процессора. Код разработан для микрокомпьютера Raspberry Pi 2/3 (листинг 7.4).
.section .data
fib_num: .asciz "Fibonacci: %d\n" // Форматированная строка для вывода
.section .bss
num1: .skip 4 // Первый элемент массива
num2: .skip 4 // Второй элемент массива
result: .skip 4 // Результат
.section .text
.global _start
.extern printf // Объявление функции printf
_start:
ldr r0, =0 // num1 = 0
str r0, num1
ldr r0, =1 // num2 = 1
str r0, num2
mov r1, #0 // Счетчик
mov r2, #10 // Количество чисел Фибоначчи для вычисления
fib_loop:
ldr r0, num1
ldr r3, num2
add r0, r0, r3 // r0 = num1 + num2
str r0, result
ldr r0, =fib_num // Указатель на строку
ldr r1, result // Результат для передачи в printf
bl printf
ldr r0, num2 // Обновление num1
str r0, num1
str r0, num2
add r1, r1, #1
cmp r1, r2
blt fib_loop // Если r1 < r2, продолжаем цикл
mov r0, #0 // Код возврата 0
bx lr // Завершение программы
В приложении А приводится краткая система команд данных процессоров.
Архитектура RISC-V представляет собой расширяемую открытую свободную систему команд, а также свободно распространяемую микроархитектуру ядра.
Свобода распространения и внесение изменений привлекли пристальное внимание со стороны как энтузиастов в академической среде, так и крупных разработчиков, производителей электроники. На сегодняшний день сформировался пул организаций, поддерживающих и развивающих данную архитектуру. В Российской Федерации также существует команда разработчиков и производителей, объединённая в "Альянс RISC-V ".
На рисунке 7.8 представлена структурная схема микроархитектуры ядра процессора RISC-V для стандартных целочисленных операций.
На рисунке структурной схемы выделены следующие блоки:
PC (program counter) - счетчик адреса памяти инструкции (Instruction memory, IMEM), указывающий на выбираемую из памяти инструкцию. В ряде источников данный счетчик обычно называется указателем команд (IP) или регистром адреса команд (IAR).
После выполнения инструкции значение PC обновляется так, чтобы указывать на следующую инструкцию в последовательности. Вне зависимости от того какая инструкция выполняется, процессор последовательно считывает инструкции, увеличивая счетчик на размер инструкций (4 байта для стандартных 32-х битных инструкций). Данная операция выполняется блоком сумматора текущего адреса и константы 4.
Если в программе есть ветвления, такие как условные операции (например, beq - branch if equal), значение PC будет изменяться не на постоянной основе, а в зависимости от результата операции. На рисунке данный блок обозначен как Branch.
Несмотря на то, что регистры общего назначения могут использоваться без каких-либо ограничений, некоторые из них, согласно разработанному application binary interface RISC-V, имеют второе имя или алиас, по которому на них могут ссылаться в программном коде.
В таблице 7.1 приводится соответствие номера регистра, его алиас (псевдоним) и назначение.
| Номер регистра | Псевдоним | Назначение |
|---|---|---|
| x0 | Zero | Константа нуля |
| x1 | Ra | Адрес возврата функции |
| x2 | Sp | Указатель стека |
| x3 | Gp | Глобальный указатель |
| x4 | Tp | Локальный указатель данных на уровне потока |
| x5-x7 | t0-t2 | Временные переменные |
| x8 | s0/fp | Указатель кадра для данных локального стека |
| x9 | s1 | Сохраняемая переменная |
| x10-x11 | a0-a1 | Возвращаемые аргументы функций |
| x12-x17 | a2-a7 | Передаваемые в функции аргументы |
| x18-x27 | s2-s11 | Сохраняемые переменные |
| x28-x31 | t3-t6 | Временные переменные |
Сохраняемые переменные (регистры) должны содержать неизменное значение до начала и по окончании вызываемой функции. Временные регистры могут изменять свои значения.
Control unit является цифровым автоматом и может быть реализован с применением микропрограммного автомата либо на комбинационной логике ( "жесткой логике ").
Необходимо обратить внимание, что в процессоре RISC-V манипуляции с данными производятся только из регистрового файла. Для манипуляции с данными их предварительно необходимо загрузить из памяти в регистры регистрового файла, выполнить операцию, а затем если это предусмотрено кодом инструкции разместить обратно в памяти.
В данной архитектуре обращение к памяти возможно только с помощью двух специальных команд: load (lw) и store (sw). В английской транскрипции данный метод организации работы с памятью называют Load/Store architecture.
Команда load (загрузка) обеспечивает считывание данных из основной памяти и занесение их в регистр процессора (в команде обычно указывается адрес ячейки памяти и номер регистра)
Пересылка информации в противоположном направлении производится командой store (сохранение).
(рис 7.8)
Подход создания базового набора инструкций с выбором необходимых расширений в RISC-V позволяет беспрепятственно развивать архитектуру без затруднений для обратной совместимости. Также обеспечивается возможность производить на единой архитектуре чипы с одинаковой эффективностью, предназначенные для различных сегментов, например, встраиваемых системы где делается упор на энергоэффективность и цену, либо пользовательские решения, где важна производительность.
Как и в остальных архитектурах, операции в RISC-V кодируются в особый формат, однако (за исключением особого расширения сжатых инструкций), инструкции имеют всегда одинаковую длину кодировки (32 бит) вне зависимости от разрядности процессора. Такой подход существенно упрощает декодирование инструкций, работу с памятью и повторное использование частей расширения непосредственно в реализациях других процессоров.
Ниже перечислены базовые инструкции работы процессора RISC-V RVI32, необходимые для собственной реализации ядра процессора:
1. Арифметические инструкции
ADD: сложение.
Пример: ADD x3, x1, x2 - сложение значений в регистрах x1 и x2, результат записывается в x3.
SUB: вычитание.
Пример: SUB x3, x1, x2 - вычитание значения x2 из x1, результат записывается в x3.
2. Логические инструкции
AND: логическое И.
Пример: AND x3, x1, x2 - побитовое И значений в регистрах x1 и x2, результат записывается в x3.
OR: логическое ИЛИ.
Пример: OR x3, x1, x2 - побитовое ИЛИ значений в регистрах x1 и x2, результат записывается в x3.
XOR: исключающее ИЛИ.
Пример: XOR x3, x1, x2 - побитовое исключающее ИЛИ, результат записывается в x3.
3. Инструкции для работы с памятью
LW: загрузка слова из памяти.
Пример: LW x3, 0(x1) - загрузка 32-битного слова из адреса, основанного на значении в x1, в регистр x3.
SW: запись слова в память.
Пример: SW x3, 0(x1) - запись 32-битного слова из x3 по адресу, основанному на значении в x1.
4. Управление потоком выполнения
JAL: переход с сохранением адреса возврата.
Пример: JAL x1, label - переход к метке label, адрес возврата сохраняется в x1.
LUI: загрузка верхней части значения.
Пример: LUI x1, 0x12345 - загрузка значения 0x12345000 в регистр x1.
BEQ: условный переход (если равно)
Пример: BEQ x1, x2, label - переход к метке label, если значения в x1 и x2 равны.
JALR: переход по адресу в регистре с сохранением адреса возврата.
Пример: JALR x1, 0(x5) # Переход к адресу, указанному в x5. Возврат по выполнении по адресу, сохраненному в x1.
AUIPC: добавление адреса текущей инструкции.
Пример: AUIPC x1, 0x10 - добавляет к адресу текущей инструкции значение 0x10, результат помещается в x1.
BNE: условный переход (если не равно).
Пример: BNE x1, x2, label - переход к метке label, если значения в x1 и x2 не равны.
5. Инструкции для работы с непосредственными данными
ADDI: сложение с немедленным значением.
Пример: ADDI x3, x1, 10 - сложение значения в x1 с 10, результат записывается в x3.
SLTI: сравнение меньше с немедленным значением.
Пример: SLTI x3, x1, 10 - x3 будет равно 1, если x1 < 10, иначе 0.
6. Инструкции для операций с битами
SLL: сдвиг влево.
Пример: SLL x3, x1, 2 - сдвиг значения в x1 на 2 бита влево, результат записывается в x3.
SRL: логический сдвиг вправо.
Пример: SRL x3, x1, 2 - логический сдвиг значения в x1 на 2 бита вправо.
Все инструкции в RISC-V разделены на 6 типов:
Форматы типов инструкций представлены на рисунке 7.9 и определены в "The RISC-V Instruction Set Manual " [6]. В приложении Б приведен перечень инструкции для базовой версии RV32I
(рис 7.9)
Рассмотрим пример кодировки инструкции add - инструкции суммирования содержимого двух регистров и записи результата в третий регистр. На рисунке 7.10 представлен пример кодировки соответствующей инструкции.
(рис 7.10)
Первая строчка отображает то, как будет записана типовая инструкция в данном расширении. Opcode - номер операции, который описан в документации на каждую инструкцию константным значением. В случае с инструкцией add это будет 0110011, rd - номер регистра назначения результата. Funct3 - указатель дешифратору операций, какую операцию реализовывать, на случай если opcode инструкций одинаковы, rs1 - номер первого операнда. rs2 - номер второго операнда. Оставшаяся часть заполняется нулями. При детальном рассмотрении типовой кодировки и кодировки конкретной инструкции можно заметить, что у нас отсутствует imm(произвольная константа), которая занимает 12 бит. Это связано с тем, что логика инструкции сделана так, что никаких констант не требуется. Освободившееся место использовано для второго операнда, положение которого не стандартизировано в типовой кодировке. Если бы у нас была константа, но не было нужды в номере регистра второго операнда, то операнд находился именно в поле с 31 по20-ый бит. Таким образом, при сложении r1 и r2 с записью результата в r3, кодировка будет иметь следующий вид: 0000000_00010_00001_000_00011_0110011.
При изучении и в сравнении с другими системами ISA можно заметить, что процессоры RISC-V на уровне микроархитектуры в силу упрощения и повышения быстродействия не реализуют множество инструкций, характерных для других решений. Поэтому в компиляторы ассемблера заложены псевдо-инструкции, которые может применять программист, а они в свою очередь будут транслированы в инструкции, реализуемые процессором. В таблицу 7.2 сведены псевдо-инструкции, транслируемые в инструкции процессоров RISC-V.
| Псевдо-инструкция | Инструкция RISC-V | Описание |
|---|---|---|
| nop | addi zero,zero,0 | нет операции |
| mv rd, rs1 | addi rd, rs, 0 | Копирование между регистрами rs в rd |
| not rd, rs1 | xori rd, rs, -1 | Запись в rd логической инверсии RS (все биты проходят операцию XOR с 1) |
| neg rd, rs1 | sub rd, x0, rs | Запись в rd числа в обратном коде rs |
| seqz rd, rs1 | sltiu rd, rs, 1 | Устанавливает результат в 1 если rs <1 |
| snez rd, rs1 | sltu rd, x0, rs | Устанавливает результат в 1 если rs не равен 0 |
| sltz rd, rs1 | slt rd, rs, x0 | Устанавливает результат в 1 если rs < 0 |
| sgtz rd, rs1 | slt rd, x0, rs | Устанавливает результат в 1 если rs >0 |
| beqz rs1, offset | beq rs, x0, offset | Переход если равно 0 |
| bnez rs1, offset | bne rs, x0, offset | Переход если не равно 0 |
| blez rs1, offset | bge x0, rs, offset | Переход если меньше или равно 0 |
| bgez rs1, offset | bge rs, x0, offset | Переход если больше или равно 0 |
| bltz rs1, offset | blt rs, x0, offset | Переход если меньше 0 |
| bgtz rs1, offset | blt x0, rs, offset | Переход если больше 0 |
| bgt rs, rt, offset | blt rt, rs, offset | Переход если больше |
| ble rs, rt, offset | bge rt, rs, offset | Переход если меньше или равно |
| bgtu rs, rt, offset | bltu rt, rs, offset | Условный переход сравнить два беззнаковых числа. Если rs >rt переход по адресу offset |
| j offset | jal x0, offset | безусловный переход |
| jr offset | jal x1, offset | Безусловный переход по адресу, адрес возврата в x1 |
| ret | jalr x0, x1, 0 | Возврат из функции |
Для рассматриваемой архитектуры также выполним вычисление чисел Фибоначчи. В листинге 7.5 приведен пример на языке ассемблера
# Начальные значения fib(0) = 0, fib(1) = 1
add x1, x0, x0 # в x1 заносим 0
add x2, x0, x1 # в x2 заносим 1
# Переменная n для хранения входящего значения
add x3, x10, x0 # x3 = n
# Проверка n (0 и 1)
add x4, x0, x0 # x4 = 0 ; fib(0)
beq x3, x4, return_zero # Если n == 0, переход к return_zero
add x4, x0, x0 # x4 = 0 ; переменная для счетчика
addi x4, x4, 1 # x4 = 1 ; стартовый индекс (fib(1))
next_fib:
add x5, x1, x2 # x5 = fib(n-1) + fib(n-2)
add x1, x2, x0 # fib(n-1) = fib(n)
add x2, x5, x0 # fib(n) = новый fib(n)
addi x4, x4, 1 # увеличиваем счетчик
bne x4, x3, next_fib # если счетчик не равен n, переходим к next_fib
finish:
add x10, x2, x0 # возвращаем результат в a0
return_zero:
add x10, x0, x0 # возвращаем 0
Стековая память в архитектуре RISC-V организуется аналогично другим архитектурам с учетом особенностей RISC-V. Как отмечалось выше основное назначение стека - временное хранение переменных при вызове функции, а также хранение адресов возврата из функций (подпрограмм).
Стек растет в выделенной для него области памяти от старших адресов к младшим. При добавлении новых данных в стек адрес стека уменьшается.
Для работы со стеком в файле регистров выделен регистр x2, имеющий алиас sp - указывает на вершину стека.
В дополнение к sp может применятся регистр fp (frame pointer), который помогает отслеживать основание текущего фрейма функции, что облегчает доступ к локальным переменным и параметрам.
Операции со стеком:
При входе в функцию устанавливается фрейм указателя fp и выделяется место для локальных переменных (пример приведен в листинге 7.6, а работа показана на рисунке 7.11).
addi sp, sp, -16; уменьшить указатель стека для выделения 16 байт
sw ra, 12(sp); сохранить адрес возврата
sw fp, 8(sp); сохранить предыдущий фрейм указателя
addi fp, sp, 16; установить новый фрейм указателя
(рис 7.11)
При завершении работы функции указатели восстанавливаются, и стек возвращается в прежнее состояние (листинг 7.7).
addi sp, sp, 16; восстановить указатель стека
lw ra, 12(sp); восстановить адрес возврата
lw fp, 8(sp); восстановить предыдущий фрейм указателя
ret; возврат из функции
Кэш - небольшая быстрая память относительно основной памяти ЭВМ, расположенная близко к процессору, чаще всего на одном с ним кристалле.
Кэш копирует оперативную память не отдельными байтами, а областями, называемыми кэш-линиями. Если в случае обращения процессора к данным они оказываются в кэше, то это называется кэш-попаданием, если же их не оказывается в кэше, то это называется кэш-промахом.
В случае кэш-попадания запрос процессора в память обрабатывается значительно быстрее чем в случае кэш-промаха, так как кэш расположен гораздо ближе к процессору, чем оперативная память.
Кэш-память включает несколько уровней, каждый из которых имеет свои характеристики:
Уровень L1
Разделение на кэш инструкций и кэш данных:
Размер: Обычно L1-кэш имеет небольшой размер, диапазон обычно от 16 KB до 128 KB. Этот уровень кэша имеет очень высокую скорость доступа.
Уровень L2
Единый или разделённый L2-кэш может быть либо единым для инструкций и данных, либо разделённым на I-cache и D-cache.
Объем больший по сравнению с L1-кэшем и может варьироваться от 256 KB до нескольких МБ. Он медленнее чем L1, но все ещё намного быстрее, чем доступ к основной памяти.
На рисунке 6.12 приведена пример схемы кэш-памяти.
(рис 7.12)
Ассоциативность кэша определяет, сколько мест (строк) может использоваться для хранения определённого блока данных.
Прямой кэш: каждый блок памяти может быть помещён только в одно определённое место в кэше. Это простейшая и наиболее быстрая организация.
Ассоциативный кэш: позволяет хранить блоки данных в нескольких местах. Например, 4-х ассоциативный кэш может разместить данные в четырёх различных местах, что снижает вероятность конфликтов при хранении.
Полностью ассоциативный кэш: данные могут храниться в любом месте кэша, что повышает гибкость, но требует сложных алгоритмов поиска.
Устройство кэш-строк:
Существуют следующие алгоритмы кэширования
Замещение: когда кэш заполнен и необходимо загрузить новый блок, используется алгоритм замещения:
Запись: при записи данных в кэш могут использоваться различные стратегии:
Кэш-память основывается на двух основных принципах локальности:
При разработке собственных решений на архитектуре RISC-V в зависимости от проекта применяется создание блока кэш-памяти. Относительно ядра процессора кэш будет внешним устройством и внедрение потребует создание блока контроллера кэш - памяти. На рисунке 7.13 приведен пример одной из разработок для RISC-V на FPGA с блоком кэш-памяти [7]. Для приведенного блока разработчиком определены следующие характеристики:
(рис 7.13)
Тактирование процессора определяет порядок его функционирования, обеспечивая синхронность процессов, выполнения операций и управления работой всех блоков.
Цикл тактирования время между двумя последовательными пиками тактового сигнала. Это время, за которое процессор может выполнить одно или несколько действий.
Критически длинный путь - время, отводимое на исполнение самой долгой инструкции.
В большинстве процессоров, включая RISC-V, выполнение инструкций делится на несколько этапов, каждый из которых обычно занимает один тактовый цикл. Эти этапы, как было отмечено в начале раздела, могут включать:
Выделяют однотактные, многоактные и конвейерные процессоры.
Однотактный (single-cycle) - процессор выполняет всю команду за один такт. Принцип работы легко объяснить, а устройство управления довольно простое. Из-за того, что все действия выполняются за один такт, эта микроархитектура не требует дополнительных регистров, требуемых для работы процессора, но недоступных для использования программистом). Однако, длительность такта ограничена самой медленной командой, использующей самый длинный критический путь.
Основным преимуществом однотактной микроархитектуры является простота понимания ее работы. К минусам можно отнести: относительно высокие аппаратные затраты из-за использования дополнительных сумматоров и раздельной основной памяти (гарвардская архитектура), низкая тактовая частота из-за длинного критического пути, так как разные инструкции проходят разный путь, скорость работы ограничена скоростью самой медленной инструкции.
Многотактный процессор (multi-cycle) - выполняет команду за несколько более коротких тактов. Простым командам нужно меньше тактов, чем сложным. Вдобавок, многотактная микроархитектура уменьшает количество необходимой аппаратуры путем повторного использования таких "дорогих " блоков, как сумматоры и блоки памяти.
Многотактная микроархитектура предполагает использование буферных регистров, с целью уменьшения критического пути и поднятия таковой частоты. При этом каждая инструкцию будет выполняться несколько более коротких тактов, используя разное количество тактов для реализации разных инструкций.
Конвейерные процессоры увеличивают производительность, позволяя нескольким инструкциям прокладывать свой путь через процессор в одно и то же время. При применении данной микроархитектуры процессор начинает декодировать новую инструкцию, в то время как предыдущая ожидает результатов. В этом случае до нескольких инструкций может находиться в обработке одновременно, позволяя процессору выполнять вычисления в несколько раз быстрее.
Приведенный на рисунке 7.8 процессор, который проектируется в данном курсе является однотактным. В дополнительной литературе [1] приводятся примеры многотактных процессоров и организации конвейера.
1. Сокращенная система команд Intel x86 (IA32)
Арифметические операции:
ADD - сложение.
Пример: add eax, ebx; сложить EAX и EBX результат в EAX
SUB - вычитание.
MUL - умножение (беззнаковое).
IMUL - умножение (знаковое).
DIV- деление (беззнаковое).
IDIV- деление (знаковое).
INC - инкремент, увеличение на 1.
DEX - декремент уменьшение на 1.
Пример: dec ecx; Уменьшение счетчика ecx на 1
Логические операции:
AND - логическое "И".
OR - логическое "ИЛИ".
XOR - исключающее "ИЛИ".
Пример: xor ebx, ebx; Очистить ebx
NOT- логическое отрицание.
Сравнения и переходы:
CMP - сравнение с установкой флага 0
Пример: cmp ecx, 0; сравнение содержимого регистра ecx с 0 в результате совпадения флаг 0 - ZF регистра состояний установится в 0;
JNE (JNZ)-переход, если не равно.
Пример: dec ecx; jnz .loop; Если ecx не достиг 0 вернуться на начало
JE - переход, если равно.
Пример: cmp ecx, 0 je .done; Если в ходе предыдущей операции ecx=0, то перейти по адресу метки done
JL - переход, если меньше.
JG - переход, если больше.
Передача управления:
CALL - вызов процедуры.
Пример: call printf; вызвать подпрограмму по адресу метки
RET - возврат из процедуры.
JMP - безусловный переход.
Загрузки и сохранения:
MOV - перемещение данных.
Пример: mov ebx, edx; записать содержимое регистра edx в ebx
MOVZX - считать содержимое источника как байт или слово с заполнением 0 до размера операнда.
Пример: movzx ecx, byte [n]; загрузить байт с заполнением нулями до размера ecx
PUSH - помещение значения в стек.
Пример: push eax; поместить в стек eax
POP - извлечение значения из стека.
Пример: pop eax; восстановить из стека eax
LOAD - загрузка из памяти (в основном через MOV).
Управление прерываниями:
INT - вызов прерывания.
Пример int 0x80; вызов прерывания операционной системы Linux
IRET - возврат из прерывания.
Системные команды (для работы с процессором и памятью):
HLT - остановка процессора.
NOP -нет операции (пустая команда).
2. Система команд Atmega328
Арифметические команды:
ADD - сложение без переноса
Пример: ADD r16, r17; сложение регистров r16 и r17 с занесением результата в r16
ADC - сложение с учетом переноса.
Пример: ADC r16, r17; сложение регистров r16 и r17 с занесением результата в r16 и формирование знака переноса
SUB - вычитание.
SBC - вычитание с учетом переноса.
MUL - умножение.
DIV - деление.
INC - инкремент, увеличение регистра на 1.
Пример: inc r20; Инкремент содержимого r20
DEC - декремент, уменьшение регистра на 1.
Логические команды:
AND - логическое И.
Пример: and r2, r3 ; Поразрядное and r2 и r3, результат поместить в r2
OR - логическое ИЛИ.
XOR - логическое исключающее ИЛИ.
Команды сдвига и поворота:
LSL - логический сдвиг влево.
LSR - логический сдвиг вправо.
ROR - циклический сдвиг вправо.
ROL - циклический сдвиг влево.
Команды сравнения:
CPI - сравнение с немедленным значением.
CP - сравнение двух регистров.
Пример: cp r20, r16; Сравнить регистры r20 и r16
Управление переходами:
JMP - безусловный переход.
CALL - вызов подпрограммы.
RET - возврат из подпрограммы.
BRNE, BEQ, BGE, BLT и другие - условные переходы.
Пример: brne <метка перехода > перейти по метке если прядущее значение равно 0
cpi r27, 5; Сравнить r27 с 5
brne loop ; Перейти если r27 < > 5
Команды работы с вводом-выводом:
MOV - запись между регистрами
Пример: mov r18, r19; Копировать r19 в r18
IN - чтение данных из порта ввода.
Пример: in r25, $16; Считать порт B
OUT - запись данных в порт вывода.
Команды работы с памятью:
LD - загрузка данных из SRAM или EEPROM.
LDI - загрузка непосредственного значения в регистр
Пример: LDI R16, 10; Загрузить в регистр R16 значение 10
ST - сохранение данных в SRAM.
Команды работы с прерываниями:
SEI - разрешение прерываний.
CLI - отключение прерываний.
RET - возврат из обслуживания прерывания.
Подробное описание инструкций на русском языке [8].
3. Общая система команд для процессоров ARM
Команды ввода вывода
LOAD (LD) - загрузка данных из памяти в регистр.
Примеры:
LDR r0, [r1]; Загружает значение из памяти по адресу R1 в регистр R0
ldr r0, =0 ; Заносит в регистр r0 значение по ссылке
STORE (STR) - сохранение данных из регистра в память.
Пример: STR r0, [r2] ; Сохраняет значение r0 в память по адресу хранящемуся в r2
Арифметические и логические команды
ADD - сложение двух регистров.
Пример: ADD R2, R0, R1; Сложить r0 и r1 результат записать r2
SUB - вычитание.
AND, ORR, EOR - логические операции.
Пример: AND R4, R2, R3; Выполнить побитно операцию И над содержимым регистров r2 и r3 результат записать в r4
MUL - умножение.
SDIV, UDIV - деление знаковых и беззнаковых чисел.
Примеры: MUL R4, R0, R1; Умножить содержимое регистров r0 и r1, результат поместить в r4; SDIV R5, R4, R2; Разделить содержимое регистра r4 на r2, результат в r5 (знаковое деление)
Команды управления переходами
B - безусловный переход.
BL - переход с вызовом подпрограммы.
BX - переход на адрес, расположенный в регистре.
Примеры: B label; Переход к метке label. BL function; Вызов функции. BX R3; Переход на адрес, находящийся в r3
Команды управления памятью
PUSH - сохранить регистры в стек.
POP - извлечь регистры из стека.
Примеры: PUSH {R0, R1}; Сохраняет R0 и R1 в стек. POP {R2, R3}; Извлекает значения из стека в R2 и R3
Подробное описание инструкций на русском языке [9].
(рис 7.14)
Процессор или центральный процессор - основное устройство любой электронно-вычислительной машины (ЭВМ) в широком понимании данного термина. Он осуществляет управление всеми элементами данной машины и выполняет основной набор вычислительных функций для конкретного решения. Упрощенный вариант процессора приведен на рисунке 7.1.
Блок управления осуществляет выборку инструкции из памяти ЭВМ, дает команды внутри процессора и управляет работой внешних устройств в том числе и памятью. Выбранная инструкция, если содержит данные в памяти, должна через дешифратор инструкций загрузить данные в соответствующие регистры процессора (условные регистр А и В). Дешифратор инструкций, если подразумевается какая-либо операция над данными, активирует соответствующий узел операционного блока. Результат инструкции может быть возвращен в память либо в один из регистров. Далее осуществляется выборка следующей инструкции или переход к другому участку памяти, в случае вызова перехода в результате исполнения инструкции.
(рис 7.1)
Архитектуру процессоров как широкого понятия можно определить набором правил, характеристик, определяющих структуру и функционирование данных устройств.
Первый вид классификации процессоров - система команд процессора (ISA - Instruction Set Architecture). ISA - набор инструкций, которые процессор может выполнять. Инструкции включает в себя как основные команды, так и формат данных, управление памятью и регистры. ISA является ключевым понятием в архитектуре микропроцессорных систем, так как определяет, каким образом программное обеспечение взаимодействует с аппаратным.
Выделяют три категории в данной классификации:
CISC (Complex Instruction Set Computer) сложный набор инструкций.
Такие процессоры могут иметь инструкции произвольной длины. Процессоры этой архитектуры способны выполнять несколько операций за несколько тактов. В архитектуре CISC обеспечивается большое количество методов адресации. В тоже время требуется условно небольшое количество регистров необходимых для реализации программы.
К данной категории процессоров относятся x86 совместимые процессоры.
RISC (Reduced Instruction Set Computer) упрощенный набор инструкций.
Процессоры RISC имеют одинаковый формат команд. Все команды имеют одинаковую длину. Инструкции исполняются за один такт за исключением сложных арифметических инструкций. RISC процессоры имеют небольшое количество команд по сравнению с CISC. Для повышения быстродействия имеют большое количество неименованных регистров, объединяемых в регистровый файл.
Для RISC процессоров признаками являются:
К данной категории процессоров относятся процессоры ARM, различные микроархитектуры микроконтроллеров, например, AVR, PIC, а также решения RISC-V.
VLIW (Very Long Instruction Word) - очень длинная машинная команда - архитектура процессоров, характеризующаяся возможностью объединения нескольких простых команд в так называемую связку. Входящие в нее команды должны быть независимы друг от друга и выполняться параллельно. Таким образом, из нескольких независимых машинных команд транслятор формирует одно очень длинное командное слово. Данная архитектура подразумевает, что длинная команда распараллеливается на несколько ядер, исполняющих каждое свою часть. При применении процессоров данной архитектуры большая часть работы выполняется компилятором языка программирования.
К данной категории процессоров относятся процессоры Эльбрус и некоторые решения IA64.
Вторым видом классификации архитектур процессоров является способ доступа к памяти. В данной классификации выделяют две категории:
1.Архитектура Фон Неймана:
Данная архитектура имеет следующие преимущества:
Недостатки:
На рисунке 7.2 приведена упрощенная схема организации данной архитектуры.
(рис 7.2)
Архитектура фон Неймана применяется x86 совместимыми процессорами и их последующими поколениями.
Гарвардская архитектура имеет следующие свойства:
Свойства данной архитектуры исключают недостатки архитектуры фон Неймана. Недостатком данной архитектуры является увеличенное количество линий связи с памятью и схемотехническая сложность интерфейса модулей памяти.
Процессоры с RISC системой команд строят по гарвардской архитектуре, для обеспечения максимальной производительности. На рисунке 7.3 приведена упрощенная схема организации гарвардской архитектуры.
(рис 7.3)
Другими специфическими особенностями классификации, скорее микроархитектур, можно отнести следующие:
По системам команд процессоры классифицируются на:
Еще одним видом классификации процессоров является степень параллелизма обработки данных:
1. SISD (Single Instruction, Single Data) - это архитектура, в которой один процессор выполняет одну инструкцию над одним набором данных в один момент времени. Это традиционная модель вычислений, используемая в большинстве обычных процессоров.
Каждый такт процессор выполняет одну инструкцию над одной частью данных, что ограничивает его производительность при обработке больших объемов данных.
Применение: подходит для простых задач и приложений, которые не требуют высокой степени параллелизма, таких как базовые вычисления и небольшие программы.
Преимущества: простота реализации и программирования.
Недостатки: низкая эффективность в сравнении с параллельными архитектурами при выполнении сложных задач.
2. SIMD (Single Instruction, Multiple Data) - это архитектура, в которой одна и та же инструкция применяется одновременно ко многим данным. Это позволяет значительно ускорить выполнение операций над большими массивами данных.
В один момент времени одна инструкция применяется ко всем элементам векторов или массивов. Например, сложение двух векторов чисел.
Применение: широко используется в обработке изображений, машинном обучении, научных вычислениях и т.д.
Преимущества: высокая производительность при обработке больших объемов однотипных данных, эффективное использование ресурсов.
Недостатки: ограниченная гибкость, поскольку не все задачи можно разбить на операции с одинаковыми данными.
3. MIMD (Multiple Instruction, Multiple Data) - это архитектура, в которой несколько процессоров могут выполнять разные инструкции над разными наборами данных одновременно. Это наиболее универсальный тип архитектуры.
Каждый процессор может выполнять свою собственную инструкцию и работать с собственными данными, что позволяет решать разнообразные задачи параллельно.
Применение: используется в многоядерных процессорах, кластерах, распределенных системах и для высокопроизводительных вычислений.
Преимущества: высокая степень гибкости и универсальности, возможность работы с разнообразными задачами.
Недостатки: более сложная реализация и необходимость в эффективной синхронизации и управлении потоками.
Кратко рассмотрим микроархитектуры процессоров, представленных на рынке.
Применяемые в большинстве решений вычислительных машин, процессоры реализованы на так называемой Intel совместимой архитектуре. Данные процессорные решения имеют разную микроархитектуру и совместимы между собой на уровне общих инструкций языка ассемблера. В то же время могут иметь собственные специфические инструкции, описываемые в документации разработчика. На рисунке 7.4 приведена общедоступная структурная схема процессора Intel Pentium [3], совместимость с которым у других производителей была наиболее близкой. На структурной схеме можно выделить следующие блоки:
(рис 7.4)
С точки зрения программиста в Intel архитектуре выделяются общие поименованные регистры, которые применяются при разработке программ на языке ассемблера. Данные регистры называют регистрами общего назначения (РОН). К некоторым из них можно обращаться секциями: байтом, словом двойным словом (для 32-разрядной x86 архитектуры). На рисунке 7.5 приведены поименованные 32-х разрядные регистры Intel x86 и форматы взаимодействия с частями данных регистров. При работе с 64-х разрядной архитектурой процессора префикс заменяется на букву R. Регистры общего назначения являются основой для разработки программ и позволяют решать основные задачи по обработке данных, таких, как хранение операндов для арифметических и логических операций, операндов вычисления адресов ячеек памяти, указателей на переменные в памяти.
Несмотря на общий характер данных регистров они всё же используются для определенных целей:
В 32-х разрядных процессорах архитектуры x86 для управления памятью применяются следующие сегментные регистры:
(рис 7.5)
Стек (стековая память) - структура данных в оперативной памяти, применяемая для временного хранения содержимого регистров процессора, например, для обработки подпрограммы прерывания. Данные упорядочиваются в стеке последовательно. Существуют следующие виды стеков:
Регистр указатель стека ESP фиксирует в себе адрес последних записанных данных.
Отдельно стоит отметить наличие регистра флагов, определяющего состояние процессора, применяемого для управления переходами.
Как видно из описания, x86 процессоры представляют собой сложную организацию с закрытой микроархитектурой. Механизмы управления и параллелизма представляют собой множество цепей управления, соответственно система управления является громоздкой и требует инженерного творчества для обеспечения высокой производительности.
Рассмотрим микроархитектуру RISC на примере восьмиразрядного микроконтроллера Atmega328P (устанавливается на все Arduino UNO отладочные платы). На рисунке 7.6 приведена упрощенная схема данного микроконтроллера со следующими основными блоками:
Расположенные справа модули представляют специфические для каждого микроконтроллера встроенные периферийные устройства, обеспечивающие сбор, прием/передачу данных и сигналов для исполнительных механизмов, т.е порты ввода-вывода данных (I/O Module).
Как видно из схемы память инструкций и данных разделены, соответственно архитектура организации памяти - гарвардская.
(рис 7.6)
ARM процессоры - это коммерческие лицензируемые ядра процессоров, построенные с использованием RISC команд, но с технологическими особенностями организации этого ядра. Компания ARM, разработчик различных инженерных решений, продает лицензии и документацию на ядро процессора конечным производителям микропроцессоров. Разработчики, используя свои периферийные модули и другие вычислительные блоки, добавляя их к ядру ARM, получают готовое решение. На рисунке 7.7 представлена упрощенная структурная схема процессора ARM9, реализованная на описаниях из разных информационных источников [4,5]. На структурной схеме можно выделить следующие блоки:
(рис 7.7)
Более подробная информация об организации работы процессоров, существующих архитектурах, методах работы с памятью приводится в литературе [1,2] и дополнительной литературе [2,3].
Для понимания различий в архитектуре рассмотренных процессоров реализуем решение задачи по вычислению чисел Фибоначчи на языках ассемблера (ISA). Предварительно приведем код для вычисления чисел Фибоначчи на языке Си (листинге 7.1).
include <stdio.h >
int main() {
int n= 10; // количество чисел Фибоначчи
unsigned long long fib[10]; // массив для хранения чисел Фибоначчи
// инициализация первых двух чисел Фибоначчи
fib[0] = 0;
fib[1] = 1;
// вычисление чисел Фибоначчи
for (int i = 2; i < n; i++) {
fib[i] = fib[i - 1] + fib[i - 2];
}
// вывод чисел Фибоначчи
printf("Первые %d чисел Фибоначчи:\n", n);
for (int i = 0; i < n; i++) {
printf("%llu ", fib[i]);
}
printf("\n");
return 0;
}
Для описанного выше примера приведем код на языке ассемблера процессора x86, реализованного в компиляторе NASM (листинге 7.2)
section .data
fmt db "Fibonacci(%d) = %d", 10, 0 ; Формат строки для вывода
n db 5; Измените здесь для получения n-го числа Фибоначчи
section .bss
result resd 1; Результат вычисления
section .text
extern printf; Импорт функции printf
global main; Точка входа в программу
main:
; Загружаем значение n
movzx ecx, byte [n]; Загружаем n в ecx
mov eax, 0; fib(0)
mov ebx, 1; fib(1)
cmp ecx, 0; Если n = 0
je .done; Переход к завершению
cmp ecx, 1; Если n = 1
je .next; Переход к следующему
; Итеративная часть
.loop:
;Вычисляем n-е число Фибоначчи
mov edx, eax ; Сохраняем предыдущую Fibonacci (fib(n-2))
add eax, ebx; fib(n) = fib(n-1) + fib(n-2)
mov ebx, edx; Обновляем fib(n-1) на fib(n-2)
dec ecx; Уменьшаем счетчик
jnz .loop; Повторяем до n = 0
.next:
; Задаем результат для вывода
mov [result], eax ; Сохраняем результат
.done:
; Вывод результата
push eax; Параметр: значение Fibonacci
push dword [n]; Параметр: n
push fmt; Строка для printf
call printf; Вызов функции printf
add esp, 12; Очистка стека
; Завершение программы
mov eax, 1; sys_exit
xor ebx, ebx; Устанавливаем код возврата 0
int 0x80; Вызов ядра
Приведем решение данной задачи на языке ассемблера для микроконтроллера Atmega328P. Код приведен в листинге 7.3.
.section .text .global fib fib: ; Сохранение регистров push r16 push r17 mov r16, r24 ; Инициализация значений ldi r18, 0; Заносим первое значение ldi r19, 1; Заносим второе значение ; Проверяем (0 или 1) cp r16, r18 breq .fib_done cp r16, r19 breq .fib_done ; Цикл вычислений ldi r20, 2 ldi r21, 0 .fib_loop: cp r20, r16 breq .fib_done ; Вычисления чисел Фибоначчи add r21, r18 mov r18, r19 mov r19, r21 ; Увеличение счетчика inc r20 ; Проверка окончания цикла .fib_loop brne .fib_loop ; Возврат по окончании вычислений call .fib_done .fib_done: mov r24, r19; Сохранение итога ; Завершение программы pop r17 pop r16 ret
Далее приводится код на ассемблере ARM процессора. Код разработан для микрокомпьютера Raspberry Pi 2/3 (листинг 7.4).
.section .data
fib_num: .asciz "Fibonacci: %d\n" // Форматированная строка для вывода
.section .bss
num1: .skip 4 // Первый элемент массива
num2: .skip 4 // Второй элемент массива
result: .skip 4 // Результат
.section .text
.global _start
.extern printf // Объявление функции printf
_start:
ldr r0, =0 // num1 = 0
str r0, num1
ldr r0, =1 // num2 = 1
str r0, num2
mov r1, #0 // Счетчик
mov r2, #10 // Количество чисел Фибоначчи для вычисления
fib_loop:
ldr r0, num1
ldr r3, num2
add r0, r0, r3 // r0 = num1 + num2
str r0, result
ldr r0, =fib_num // Указатель на строку
ldr r1, result // Результат для передачи в printf
bl printf
ldr r0, num2 // Обновление num1
str r0, num1
str r0, num2
add r1, r1, #1
cmp r1, r2
blt fib_loop // Если r1 < r2, продолжаем цикл
mov r0, #0 // Код возврата 0
bx lr // Завершение программы
В приложении А приводится краткая система команд данных процессоров.
Архитектура RISC-V представляет собой расширяемую открытую свободную систему команд, а также свободно распространяемую микроархитектуру ядра.
Свобода распространения и внесение изменений привлекли пристальное внимание со стороны как энтузиастов в академической среде, так и крупных разработчиков, производителей электроники. На сегодняшний день сформировался пул организаций, поддерживающих и развивающих данную архитектуру. В Российской Федерации также существует команда разработчиков и производителей, объединённая в "Альянс RISC-V ".
На рисунке 7.8 представлена структурная схема микроархитектуры ядра процессора RISC-V для стандартных целочисленных операций.
На рисунке структурной схемы выделены следующие блоки:
PC (program counter) - счетчик адреса памяти инструкции (Instruction memory, IMEM), указывающий на выбираемую из памяти инструкцию. В ряде источников данный счетчик обычно называется указателем команд (IP) или регистром адреса команд (IAR).
После выполнения инструкции значение PC обновляется так, чтобы указывать на следующую инструкцию в последовательности. Вне зависимости от того какая инструкция выполняется, процессор последовательно считывает инструкции, увеличивая счетчик на размер инструкций (4 байта для стандартных 32-х битных инструкций). Данная операция выполняется блоком сумматора текущего адреса и константы 4.
Если в программе есть ветвления, такие как условные операции (например, beq - branch if equal), значение PC будет изменяться не на постоянной основе, а в зависимости от результата операции. На рисунке данный блок обозначен как Branch.
Несмотря на то, что регистры общего назначения могут использоваться без каких-либо ограничений, некоторые из них, согласно разработанному application binary interface RISC-V, имеют второе имя или алиас, по которому на них могут ссылаться в программном коде.
В таблице 7.1 приводится соответствие номера регистра, его алиас (псевдоним) и назначение.
| Номер регистра | Псевдоним | Назначение |
|---|---|---|
| x0 | Zero | Константа нуля |
| x1 | Ra | Адрес возврата функции |
| x2 | Sp | Указатель стека |
| x3 | Gp | Глобальный указатель |
| x4 | Tp | Локальный указатель данных на уровне потока |
| x5-x7 | t0-t2 | Временные переменные |
| x8 | s0/fp | Указатель кадра для данных локального стека |
| x9 | s1 | Сохраняемая переменная |
| x10-x11 | a0-a1 | Возвращаемые аргументы функций |
| x12-x17 | a2-a7 | Передаваемые в функции аргументы |
| x18-x27 | s2-s11 | Сохраняемые переменные |
| x28-x31 | t3-t6 | Временные переменные |
Сохраняемые переменные (регистры) должны содержать неизменное значение до начала и по окончании вызываемой функции. Временные регистры могут изменять свои значения.
Control unit является цифровым автоматом и может быть реализован с применением микропрограммного автомата либо на комбинационной логике ( "жесткой логике ").
Необходимо обратить внимание, что в процессоре RISC-V манипуляции с данными производятся только из регистрового файла. Для манипуляции с данными их предварительно необходимо загрузить из памяти в регистры регистрового файла, выполнить операцию, а затем если это предусмотрено кодом инструкции разместить обратно в памяти.
В данной архитектуре обращение к памяти возможно только с помощью двух специальных команд: load (lw) и store (sw). В английской транскрипции данный метод организации работы с памятью называют Load/Store architecture.
Команда load (загрузка) обеспечивает считывание данных из основной памяти и занесение их в регистр процессора (в команде обычно указывается адрес ячейки памяти и номер регистра)
Пересылка информации в противоположном направлении производится командой store (сохранение).
(рис 7.8)
Подход создания базового набора инструкций с выбором необходимых расширений в RISC-V позволяет беспрепятственно развивать архитектуру без затруднений для обратной совместимости. Также обеспечивается возможность производить на единой архитектуре чипы с одинаковой эффективностью, предназначенные для различных сегментов, например, встраиваемых системы где делается упор на энергоэффективность и цену, либо пользовательские решения, где важна производительность.
Как и в остальных архитектурах, операции в RISC-V кодируются в особый формат, однако (за исключением особого расширения сжатых инструкций), инструкции имеют всегда одинаковую длину кодировки (32 бит) вне зависимости от разрядности процессора. Такой подход существенно упрощает декодирование инструкций, работу с памятью и повторное использование частей расширения непосредственно в реализациях других процессоров.
Ниже перечислены базовые инструкции работы процессора RISC-V RVI32, необходимые для собственной реализации ядра процессора:
1. Арифметические инструкции
ADD: сложение.
Пример: ADD x3, x1, x2 - сложение значений в регистрах x1 и x2, результат записывается в x3.
SUB: вычитание.
Пример: SUB x3, x1, x2 - вычитание значения x2 из x1, результат записывается в x3.
2. Логические инструкции
AND: логическое И.
Пример: AND x3, x1, x2 - побитовое И значений в регистрах x1 и x2, результат записывается в x3.
OR: логическое ИЛИ.
Пример: OR x3, x1, x2 - побитовое ИЛИ значений в регистрах x1 и x2, результат записывается в x3.
XOR: исключающее ИЛИ.
Пример: XOR x3, x1, x2 - побитовое исключающее ИЛИ, результат записывается в x3.
3. Инструкции для работы с памятью
LW: загрузка слова из памяти.
Пример: LW x3, 0(x1) - загрузка 32-битного слова из адреса, основанного на значении в x1, в регистр x3.
SW: запись слова в память.
Пример: SW x3, 0(x1) - запись 32-битного слова из x3 по адресу, основанному на значении в x1.
4. Управление потоком выполнения
JAL: переход с сохранением адреса возврата.
Пример: JAL x1, label - переход к метке label, адрес возврата сохраняется в x1.
LUI: загрузка верхней части значения.
Пример: LUI x1, 0x12345 - загрузка значения 0x12345000 в регистр x1.
BEQ: условный переход (если равно)
Пример: BEQ x1, x2, label - переход к метке label, если значения в x1 и x2 равны.
JALR: переход по адресу в регистре с сохранением адреса возврата.
Пример: JALR x1, 0(x5) # Переход к адресу, указанному в x5. Возврат по выполнении по адресу, сохраненному в x1.
AUIPC: добавление адреса текущей инструкции.
Пример: AUIPC x1, 0x10 - добавляет к адресу текущей инструкции значение 0x10, результат помещается в x1.
BNE: условный переход (если не равно).
Пример: BNE x1, x2, label - переход к метке label, если значения в x1 и x2 не равны.
5. Инструкции для работы с непосредственными данными
ADDI: сложение с немедленным значением.
Пример: ADDI x3, x1, 10 - сложение значения в x1 с 10, результат записывается в x3.
SLTI: сравнение меньше с немедленным значением.
Пример: SLTI x3, x1, 10 - x3 будет равно 1, если x1 < 10, иначе 0.
6. Инструкции для операций с битами
SLL: сдвиг влево.
Пример: SLL x3, x1, 2 - сдвиг значения в x1 на 2 бита влево, результат записывается в x3.
SRL: логический сдвиг вправо.
Пример: SRL x3, x1, 2 - логический сдвиг значения в x1 на 2 бита вправо.
Все инструкции в RISC-V разделены на 6 типов:
Форматы типов инструкций представлены на рисунке 7.9 и определены в "The RISC-V Instruction Set Manual " [6]. В приложении Б приведен перечень инструкции для базовой версии RV32I
(рис 7.9)
Рассмотрим пример кодировки инструкции add - инструкции суммирования содержимого двух регистров и записи результата в третий регистр. На рисунке 7.10 представлен пример кодировки соответствующей инструкции.
(рис 7.10)
Первая строчка отображает то, как будет записана типовая инструкция в данном расширении. Opcode - номер операции, который описан в документации на каждую инструкцию константным значением. В случае с инструкцией add это будет 0110011, rd - номер регистра назначения результата. Funct3 - указатель дешифратору операций, какую операцию реализовывать, на случай если opcode инструкций одинаковы, rs1 - номер первого операнда. rs2 - номер второго операнда. Оставшаяся часть заполняется нулями. При детальном рассмотрении типовой кодировки и кодировки конкретной инструкции можно заметить, что у нас отсутствует imm(произвольная константа), которая занимает 12 бит. Это связано с тем, что логика инструкции сделана так, что никаких констант не требуется. Освободившееся место использовано для второго операнда, положение которого не стандартизировано в типовой кодировке. Если бы у нас была константа, но не было нужды в номере регистра второго операнда, то операнд находился именно в поле с 31 по20-ый бит. Таким образом, при сложении r1 и r2 с записью результата в r3, кодировка будет иметь следующий вид: 0000000_00010_00001_000_00011_0110011.
При изучении и в сравнении с другими системами ISA можно заметить, что процессоры RISC-V на уровне микроархитектуры в силу упрощения и повышения быстродействия не реализуют множество инструкций, характерных для других решений. Поэтому в компиляторы ассемблера заложены псевдо-инструкции, которые может применять программист, а они в свою очередь будут транслированы в инструкции, реализуемые процессором. В таблицу 7.2 сведены псевдо-инструкции, транслируемые в инструкции процессоров RISC-V.
| Псевдо-инструкция | Инструкция RISC-V | Описание |
|---|---|---|
| nop | addi zero,zero,0 | нет операции |
| mv rd, rs1 | addi rd, rs, 0 | Копирование между регистрами rs в rd |
| not rd, rs1 | xori rd, rs, -1 | Запись в rd логической инверсии RS (все биты проходят операцию XOR с 1) |
| neg rd, rs1 | sub rd, x0, rs | Запись в rd числа в обратном коде rs |
| seqz rd, rs1 | sltiu rd, rs, 1 | Устанавливает результат в 1 если rs <1 |
| snez rd, rs1 | sltu rd, x0, rs | Устанавливает результат в 1 если rs не равен 0 |
| sltz rd, rs1 | slt rd, rs, x0 | Устанавливает результат в 1 если rs < 0 |
| sgtz rd, rs1 | slt rd, x0, rs | Устанавливает результат в 1 если rs >0 |
| beqz rs1, offset | beq rs, x0, offset | Переход если равно 0 |
| bnez rs1, offset | bne rs, x0, offset | Переход если не равно 0 |
| blez rs1, offset | bge x0, rs, offset | Переход если меньше или равно 0 |
| bgez rs1, offset | bge rs, x0, offset | Переход если больше или равно 0 |
| bltz rs1, offset | blt rs, x0, offset | Переход если меньше 0 |
| bgtz rs1, offset | blt x0, rs, offset | Переход если больше 0 |
| bgt rs, rt, offset | blt rt, rs, offset | Переход если больше |
| ble rs, rt, offset | bge rt, rs, offset | Переход если меньше или равно |
| bgtu rs, rt, offset | bltu rt, rs, offset | Условный переход сравнить два беззнаковых числа. Если rs >rt переход по адресу offset |
| j offset | jal x0, offset | безусловный переход |
| jr offset | jal x1, offset | Безусловный переход по адресу, адрес возврата в x1 |
| ret | jalr x0, x1, 0 | Возврат из функции |
Для рассматриваемой архитектуры также выполним вычисление чисел Фибоначчи. В листинге 7.5 приведен пример на языке ассемблера
# Начальные значения fib(0) = 0, fib(1) = 1
add x1, x0, x0 # в x1 заносим 0
add x2, x0, x1 # в x2 заносим 1
# Переменная n для хранения входящего значения
add x3, x10, x0 # x3 = n
# Проверка n (0 и 1)
add x4, x0, x0 # x4 = 0 ; fib(0)
beq x3, x4, return_zero # Если n == 0, переход к return_zero
add x4, x0, x0 # x4 = 0 ; переменная для счетчика
addi x4, x4, 1 # x4 = 1 ; стартовый индекс (fib(1))
next_fib:
add x5, x1, x2 # x5 = fib(n-1) + fib(n-2)
add x1, x2, x0 # fib(n-1) = fib(n)
add x2, x5, x0 # fib(n) = новый fib(n)
addi x4, x4, 1 # увеличиваем счетчик
bne x4, x3, next_fib # если счетчик не равен n, переходим к next_fib
finish:
add x10, x2, x0 # возвращаем результат в a0
return_zero:
add x10, x0, x0 # возвращаем 0
Стековая память в архитектуре RISC-V организуется аналогично другим архитектурам с учетом особенностей RISC-V. Как отмечалось выше основное назначение стека - временное хранение переменных при вызове функции, а также хранение адресов возврата из функций (подпрограмм).
Стек растет в выделенной для него области памяти от старших адресов к младшим. При добавлении новых данных в стек адрес стека уменьшается.
Для работы со стеком в файле регистров выделен регистр x2, имеющий алиас sp - указывает на вершину стека.
В дополнение к sp может применятся регистр fp (frame pointer), который помогает отслеживать основание текущего фрейма функции, что облегчает доступ к локальным переменным и параметрам.
Операции со стеком:
При входе в функцию устанавливается фрейм указателя fp и выделяется место для локальных переменных (пример приведен в листинге 7.6, а работа показана на рисунке 7.11).
addi sp, sp, -16; уменьшить указатель стека для выделения 16 байт
sw ra, 12(sp); сохранить адрес возврата
sw fp, 8(sp); сохранить предыдущий фрейм указателя
addi fp, sp, 16; установить новый фрейм указателя
(рис 7.11)
При завершении работы функции указатели восстанавливаются, и стек возвращается в прежнее состояние (листинг 7.7).
addi sp, sp, 16; восстановить указатель стека
lw ra, 12(sp); восстановить адрес возврата
lw fp, 8(sp); восстановить предыдущий фрейм указателя
ret; возврат из функции
Кэш - небольшая быстрая память относительно основной памяти ЭВМ, расположенная близко к процессору, чаще всего на одном с ним кристалле.
Кэш копирует оперативную память не отдельными байтами, а областями, называемыми кэш-линиями. Если в случае обращения процессора к данным они оказываются в кэше, то это называется кэш-попаданием, если же их не оказывается в кэше, то это называется кэш-промахом.
В случае кэш-попадания запрос процессора в память обрабатывается значительно быстрее чем в случае кэш-промаха, так как кэш расположен гораздо ближе к процессору, чем оперативная память.
Кэш-память включает несколько уровней, каждый из которых имеет свои характеристики:
Уровень L1
Разделение на кэш инструкций и кэш данных:
Размер: Обычно L1-кэш имеет небольшой размер, диапазон обычно от 16 KB до 128 KB. Этот уровень кэша имеет очень высокую скорость доступа.
Уровень L2
Единый или разделённый L2-кэш может быть либо единым для инструкций и данных, либо разделённым на I-cache и D-cache.
Объем больший по сравнению с L1-кэшем и может варьироваться от 256 KB до нескольких МБ. Он медленнее чем L1, но все ещё намного быстрее, чем доступ к основной памяти.
На рисунке 6.12 приведена пример схемы кэш-памяти.
(рис 7.12)
Ассоциативность кэша определяет, сколько мест (строк) может использоваться для хранения определённого блока данных.
Прямой кэш: каждый блок памяти может быть помещён только в одно определённое место в кэше. Это простейшая и наиболее быстрая организация.
Ассоциативный кэш: позволяет хранить блоки данных в нескольких местах. Например, 4-х ассоциативный кэш может разместить данные в четырёх различных местах, что снижает вероятность конфликтов при хранении.
Полностью ассоциативный кэш: данные могут храниться в любом месте кэша, что повышает гибкость, но требует сложных алгоритмов поиска.
Устройство кэш-строк:
Существуют следующие алгоритмы кэширования
Замещение: когда кэш заполнен и необходимо загрузить новый блок, используется алгоритм замещения:
Запись: при записи данных в кэш могут использоваться различные стратегии:
Кэш-память основывается на двух основных принципах локальности:
При разработке собственных решений на архитектуре RISC-V в зависимости от проекта применяется создание блока кэш-памяти. Относительно ядра процессора кэш будет внешним устройством и внедрение потребует создание блока контроллера кэш - памяти. На рисунке 7.13 приведен пример одной из разработок для RISC-V на FPGA с блоком кэш-памяти [7]. Для приведенного блока разработчиком определены следующие характеристики:
(рис 7.13)
Тактирование процессора определяет порядок его функционирования, обеспечивая синхронность процессов, выполнения операций и управления работой всех блоков.
Цикл тактирования время между двумя последовательными пиками тактового сигнала. Это время, за которое процессор может выполнить одно или несколько действий.
Критически длинный путь - время, отводимое на исполнение самой долгой инструкции.
В большинстве процессоров, включая RISC-V, выполнение инструкций делится на несколько этапов, каждый из которых обычно занимает один тактовый цикл. Эти этапы, как было отмечено в начале раздела, могут включать:
Выделяют однотактные, многоактные и конвейерные процессоры.
Однотактный (single-cycle) - процессор выполняет всю команду за один такт. Принцип работы легко объяснить, а устройство управления довольно простое. Из-за того, что все действия выполняются за один такт, эта микроархитектура не требует дополнительных регистров, требуемых для работы процессора, но недоступных для использования программистом). Однако, длительность такта ограничена самой медленной командой, использующей самый длинный критический путь.
Основным преимуществом однотактной микроархитектуры является простота понимания ее работы. К минусам можно отнести: относительно высокие аппаратные затраты из-за использования дополнительных сумматоров и раздельной основной памяти (гарвардская архитектура), низкая тактовая частота из-за длинного критического пути, так как разные инструкции проходят разный путь, скорость работы ограничена скоростью самой медленной инструкции.
Многотактный процессор (multi-cycle) - выполняет команду за несколько более коротких тактов. Простым командам нужно меньше тактов, чем сложным. Вдобавок, многотактная микроархитектура уменьшает количество необходимой аппаратуры путем повторного использования таких "дорогих " блоков, как сумматоры и блоки памяти.
Многотактная микроархитектура предполагает использование буферных регистров, с целью уменьшения критического пути и поднятия таковой частоты. При этом каждая инструкцию будет выполняться несколько более коротких тактов, используя разное количество тактов для реализации разных инструкций.
Конвейерные процессоры увеличивают производительность, позволяя нескольким инструкциям прокладывать свой путь через процессор в одно и то же время. При применении данной микроархитектуры процессор начинает декодировать новую инструкцию, в то время как предыдущая ожидает результатов. В этом случае до нескольких инструкций может находиться в обработке одновременно, позволяя процессору выполнять вычисления в несколько раз быстрее.
Приведенный на рисунке 7.8 процессор, который проектируется в данном курсе является однотактным. В дополнительной литературе [1] приводятся примеры многотактных процессоров и организации конвейера.
1. Сокращенная система команд Intel x86 (IA32)
Арифметические операции:
ADD - сложение.
Пример: add eax, ebx; сложить EAX и EBX результат в EAX
SUB - вычитание.
MUL - умножение (беззнаковое).
IMUL - умножение (знаковое).
DIV- деление (беззнаковое).
IDIV- деление (знаковое).
INC - инкремент, увеличение на 1.
DEX - декремент уменьшение на 1.
Пример: dec ecx; Уменьшение счетчика ecx на 1
Логические операции:
AND - логическое "И".
OR - логическое "ИЛИ".
XOR - исключающее "ИЛИ".
Пример: xor ebx, ebx; Очистить ebx
NOT- логическое отрицание.
Сравнения и переходы:
CMP - сравнение с установкой флага 0
Пример: cmp ecx, 0; сравнение содержимого регистра ecx с 0 в результате совпадения флаг 0 - ZF регистра состояний установится в 0;
JNE (JNZ)-переход, если не равно.
Пример: dec ecx; jnz .loop; Если ecx не достиг 0 вернуться на начало
JE - переход, если равно.
Пример: cmp ecx, 0 je .done; Если в ходе предыдущей операции ecx=0, то перейти по адресу метки done
JL - переход, если меньше.
JG - переход, если больше.
Передача управления:
CALL - вызов процедуры.
Пример: call printf; вызвать подпрограмму по адресу метки
RET - возврат из процедуры.
JMP - безусловный переход.
Загрузки и сохранения:
MOV - перемещение данных.
Пример: mov ebx, edx; записать содержимое регистра edx в ebx
MOVZX - считать содержимое источника как байт или слово с заполнением 0 до размера операнда.
Пример: movzx ecx, byte [n]; загрузить байт с заполнением нулями до размера ecx
PUSH - помещение значения в стек.
Пример: push eax; поместить в стек eax
POP - извлечение значения из стека.
Пример: pop eax; восстановить из стека eax
LOAD - загрузка из памяти (в основном через MOV).
Управление прерываниями:
INT - вызов прерывания.
Пример int 0x80; вызов прерывания операционной системы Linux
IRET - возврат из прерывания.
Системные команды (для работы с процессором и памятью):
HLT - остановка процессора.
NOP -нет операции (пустая команда).
2. Система команд Atmega328
Арифметические команды:
ADD - сложение без переноса
Пример: ADD r16, r17; сложение регистров r16 и r17 с занесением результата в r16
ADC - сложение с учетом переноса.
Пример: ADC r16, r17; сложение регистров r16 и r17 с занесением результата в r16 и формирование знака переноса
SUB - вычитание.
SBC - вычитание с учетом переноса.
MUL - умножение.
DIV - деление.
INC - инкремент, увеличение регистра на 1.
Пример: inc r20; Инкремент содержимого r20
DEC - декремент, уменьшение регистра на 1.
Логические команды:
AND - логическое И.
Пример: and r2, r3 ; Поразрядное and r2 и r3, результат поместить в r2
OR - логическое ИЛИ.
XOR - логическое исключающее ИЛИ.
Команды сдвига и поворота:
LSL - логический сдвиг влево.
LSR - логический сдвиг вправо.
ROR - циклический сдвиг вправо.
ROL - циклический сдвиг влево.
Команды сравнения:
CPI - сравнение с немедленным значением.
CP - сравнение двух регистров.
Пример: cp r20, r16; Сравнить регистры r20 и r16
Управление переходами:
JMP - безусловный переход.
CALL - вызов подпрограммы.
RET - возврат из подпрограммы.
BRNE, BEQ, BGE, BLT и другие - условные переходы.
Пример: brne <метка перехода > перейти по метке если прядущее значение равно 0
cpi r27, 5; Сравнить r27 с 5
brne loop ; Перейти если r27 < > 5
Команды работы с вводом-выводом:
MOV - запись между регистрами
Пример: mov r18, r19; Копировать r19 в r18
IN - чтение данных из порта ввода.
Пример: in r25, $16; Считать порт B
OUT - запись данных в порт вывода.
Команды работы с памятью:
LD - загрузка данных из SRAM или EEPROM.
LDI - загрузка непосредственного значения в регистр
Пример: LDI R16, 10; Загрузить в регистр R16 значение 10
ST - сохранение данных в SRAM.
Команды работы с прерываниями:
SEI - разрешение прерываний.
CLI - отключение прерываний.
RET - возврат из обслуживания прерывания.
Подробное описание инструкций на русском языке [8].
3. Общая система команд для процессоров ARM
Команды ввода вывода
LOAD (LD) - загрузка данных из памяти в регистр.
Примеры:
LDR r0, [r1]; Загружает значение из памяти по адресу R1 в регистр R0
ldr r0, =0 ; Заносит в регистр r0 значение по ссылке
STORE (STR) - сохранение данных из регистра в память.
Пример: STR r0, [r2] ; Сохраняет значение r0 в память по адресу хранящемуся в r2
Арифметические и логические команды
ADD - сложение двух регистров.
Пример: ADD R2, R0, R1; Сложить r0 и r1 результат записать r2
SUB - вычитание.
AND, ORR, EOR - логические операции.
Пример: AND R4, R2, R3; Выполнить побитно операцию И над содержимым регистров r2 и r3 результат записать в r4
MUL - умножение.
SDIV, UDIV - деление знаковых и беззнаковых чисел.
Примеры: MUL R4, R0, R1; Умножить содержимое регистров r0 и r1, результат поместить в r4; SDIV R5, R4, R2; Разделить содержимое регистра r4 на r2, результат в r5 (знаковое деление)
Команды управления переходами
B - безусловный переход.
BL - переход с вызовом подпрограммы.
BX - переход на адрес, расположенный в регистре.
Примеры: B label; Переход к метке label. BL function; Вызов функции. BX R3; Переход на адрес, находящийся в r3
Команды управления памятью
PUSH - сохранить регистры в стек.
POP - извлечь регистры из стека.
Примеры: PUSH {R0, R1}; Сохраняет R0 и R1 в стек. POP {R2, R3}; Извлекает значения из стека в R2 и R3
Подробное описание инструкций на русском языке [9].
(рис 7.14)
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.