Arm команди асемблера. Вивчення системи команд процесора ARM

Arm команди асемблера. Вивчення системи команд процесора ARM

Привіт всім!
За діяльністю я програміст на Java. Останні місяці роботи змусили мене познайомитися з розробкою під Android NDK і відповідно. нативних додатківна С. Тут я зіткнувся з проблемою оптимізації бібліотек Linux. Багато хто виявився абсолютно не оптимізований під ARM і сильно навантажував процесор. Раніше я практично не програмував на асемблері, тому спочатку було складно почати вивчати цю мову, але все ж таки я вирішив спробувати. Цю статтю написано, так би мовити, від новачка для новачків. Я постараюся описати ті основи, які вже вивчив, сподіваюся, когось це зацікавить. Крім того, радий конструктивній критиці з боку професіоналів.

Вступ
Отже, спочатку розберемося що таке ARM. Вікіпедія дає таке визначення:

Архітектура ARM (Advanced RISC Machine, Acorn RISC Machine, вдосконалена RISC-машина) - сімейство 32-бітних і 64-бітових мікропроцесорних ядер, що ліцензуються розробки компанії ARM Limited. Компанія займається виключно розробкою ядер та інструментів для них (компілятори, засоби налагодження тощо), заробляючи на ліцензуванні архітектури стороннім виробникам.

Якщо хто не знає, зараз більша частина мобільних пристроїв, планшети розроблені саме на цій архітектурі процесорів. Основною перевагою даного сімейства є низьке енергоспоживаннязавдяки чому він часто використовується в різних вбудованих системах. Архітектура розвивалася з часом, і починаючи з ARMv7 було визначено 3 профілю: 'A'(application) - додатки, 'R'(real time) - у час,'M'(microcontroller) - микроконтроллер. Історію розробки цієї технології та інші цікаві дані ви можете прочитати у Вікіпедії або погугливши в інтернеті. ARM підтримує різні режими роботи (Thumb та ARM, крім того останнім часом з'явився Thumb-2, що є сумішшю ARM та Thumb). У цій статті розглянемо власне режим ARM, у якому здійснюється 32-бітовий набір команд.

Кожен процесор ARM створений з наступних блоків:

  • 37 регістрів (з яких видимих ​​при розробці лише 17)
  • Арифметико-логічний пристрій (АЛУ) - виконує арифметичні та логічні завдання
  • Barrel shifter - пристрій, створений для переміщення блоків даних на певну кількість біт
  • The CP15 - спеціальна система, що контролює ARM співпроцесори
  • Декодер інструкцій - займається перетворенням інструкції на послідовність мікрооперацій
Це не всі складові ARM, але поглиблення в нетрі побудови процесорів не входить до цієї статті.
Конвеєрне виконання (Pipeline execution)
В ARM процесорах використовується 3-стадійний конвеєр (починаючи з ARM8 був реалізований 5-стадійний конвеєр). Розглянемо простий конвеєр з прикладу процесора ARM7TDMI. Виконання кожної інструкції складається з трьох ступенів:

1. Етап вибірки (F)
На цьому етапі інструкції надходять із ОЗП в конвеєр процесора.
2. Етап декодування (D)
Інструкції декодуються та розпізнається їх тип.
3. Етап виконання (E)
Дані надходять в ALU і виконуються та отримане значення записується в заданий регістр.

Але при розробці треба враховувати, що є інструкції, які використовують кілька циклів виконання, наприклад, load(LDR) або store. У такому разі етап виконання (E) поділяється на етапи (E1, E2, E3...).

Умовне виконання
Одна з найважливіших функцій ARM асемблера – умовне виконання. Кожна інструкція може виконуватися умовно, і для цього використовуються суфікси. Якщо суфікс додається до назви інструкції, перш ніж виконати її, відбувається перевірка параметрів. Якщо параметри не відповідають умові, інструкція не виконується. Суфікси:
MI - негативне число
PL - позитивне або нуль
AL - виконувати інструкцію завжди
Суфіксів умовного виконання набагато більше. Інші суфікси та приклади прочитати в офіційній документації: ARM документація
А тепер настав час розглянути…
Основи синтаксису ARM асемблера
Тим, хто раніше працював із асемблером, цей пункт можна фактично пропустити. Для решти опишу основи роботи з цією мовою. Отже, кожна програма на асемблері складається з інструкцій. Інструкція створюється таким чином:
(мітка) (інструкція | операнди) (@ коментар)
Мітка – необов'язковий параметр. Інструкція – безпосередньо мнемоніка інструкції процесору. Основні інструкції та їх використання буде розібрано далі. Операнди - константи, адреси регістрів, адреси оперативної пам'яті. Коментар - необов'язковий параметр, який впливає виконання програми.
Імена регістрів
Дозволено такі імена регістрів:
1.r0-r15

3.v1-v8 (змінні регістри, з r4 по r11)

4.sb and SB (статичний регістр, r9)

5.sl і SL (r10)

6.fp and FP (R11)

7.ip and IP (R12)

8.sp and SP (R13)

9.lr та LR (r14)

10.pc and PC (програмний лічильник, r15).

Змінні та костанти
В асимблері ARM, як і будь-якій (практично) іншій мові програмування можуть використовуватися змінні та константи. Вони поділяються на такі типи:
  • Числові
  • Логічні
  • Рядкові
Числові змінні ініціалізуються так:
a SETA 100; створюється числова змінна "a" зі значенням 100.
Рядкові змінні:
improb SETS "literal"; створюється змінна improb з значенням «literal». УВАГА! Значення змінної не може перевищувати 5120 символів.
У логічних змінних відповідно використовуються значення TRUE та FALSE.
Приклади інструкцій ARM асемблера
У цій таблиці я зібрав основні інструкції, яка буде потрібна для подальшої розробки (на самому базовому етапі:):

Щоб закріпити використання основних інструкцій, давайте напишемо кілька простих прикладів, але спочатку нам знадобиться arm toolchain. Я працюю в Linux тому вибрав: frank.harvard.edu/~coldwell/toolchain (arm-unknown-linux-gnu toolchain). Ставиться він простіше простого, як будь-яка інша програма на Linux. У моєму випадку (Russian Fedora) знадобилося лише встановити rpm пакети із сайту.
Тепер настав час написати найпростіший приклад. Програма буде абсолютно марною, але головне, що працюватиме:) Ось код, який я вам пропоную:
start: @ Необов'язковий рядок, що означає початок програми mov r0, #3 @ Завантажуємо в регістр r0 значення 3 mov r1, #2 @ Робимо теж саме з регістром r1, тільки тепер із значенням 2 add r2, r1, r0 @ Складаємо значення r0 і r1, відповідь записуємо в r2 mul r3, r1, r0 @ Помножуємо значення регістру r1 на значення регістра r0, відповідь записуємо в r3 stop: b stop @ Рядок завершення програми
Компілюємо програму до отримання.bin файлу:
/usr/arm/bin/arm-unknown-linux-gnu-as -o arm.o arm.s /usr/arm/bin/arm-unknown-linux-gnu-ld -Ttext=0x0 -o ​​arm.elf arm .o /usr/arm/bin/arm-unknown-linux-gnu-objcopy -O binary arm.elf arm.bin
(код у файлі arm.s, а toolchain в моєму випадку лежить у директорії /usr/arm/bin/)
Якщо все пройшло успішно, у вас буде 3 файли: arm.s (власне код), arm.o, arm.elf, arm.bin (власне програма, що виконується). Для того, щоб перевірити роботу програми, не обов'язково мати власний arm пристрій. Достатньо встановити QEMU. Для довідки:

QEMU – вільна програма з відкритим вихідним кодомдля емуляції апаратного забезпечення різних платформ.

Включає емуляцію процесорів Intel x86 та пристроїв введення-виведення. Може емулювати 80386, 80486, Pentium, Pentium Pro, AMD64 та інші x86-сумісні процесори; PowerPC, ARM, MIPS, SPARC, SPARC64, m68k – лише частково.

Працює на Syllable, FreeBSD, FreeDOS, Linux, Windows 9x, Windows 2000, Mac OS X, QNX, Android та ін.

Отже, для емуляції арм знадобиться qemu-system-arm. Цей пакет є в yum, тому тим, у кого Fedora, можна не морочитися і просто виконати команду:
yum install qemu-system-arm

Далі треба запустити емулятор ARM так, щоб він виконав нашу програму arm.bin. Для цього створимо файл flash.bin, який буде флеш-пам'яттю для QEMU. Зробити це дуже просто:
dd if=/dev/zero of=flash.bin bs=4096 count=4096 dd if=arm.bin of=flash.bin bs=4096 conv=notrunc
Тепер вантажимо QEMU з отриманою flash пам'яттю:
qemu-system-arm -M connex -pflash flash.bin -nographic -serial /dev/null
На виході ви отримаєте щось на кшталт цього:

$ qemu-system-arm -M connex -pflash flash.bin -nographic -serial /dev/null
QEMU 0.15.1 monitor - type "help" for more information
(qemu)

Наша програма arm.bin мала змінити значення чотирьох регістрів, отже для перевірки правильності роботи давайте подивимося на ці самі регістри. Робиться це дуже простою командою: info registers
На виході ви побачите всі 15 ARM регістрів, причому чотири з них будуть змінені значення. Перевірте:) Значення регістрів збігаються з тими, які можна очікувати після виконання програми:
(qemu) info registers R00=00000003 R01=00000002 R02=00000005 R03=00000006 R04=00000000 R05=00000000 R06=00000000 R000 000000 R10=00000000 R11=00000000 R12=00000000 R13=00000000 R14=00000000 R15=00000010 PSR=400001d3 -Z-- A svc32

P.S. У цій статті я постарався описати основи програмування на ARM асемблер. Сподіваюсь вам сподобалось! Цього вистачить для того, щоб далі заглиблюватись у нетрі цієї мови та писати на ній програми. Якщо все вийде, писатиму далі про те, що дізнаюся сам. Якщо є помилки, прошу не штовхати, тому що я новачок в асемблері.

GBA ASM - День 2: Небагато інформації про асемблера ARM - Архів WASM.RU

ARM – це компанія, яка робить процесор GBA. Процесори ARM є RISC-процесорами (на відміну процесорів INTEL). RISC розшифровується як Reduced Instruction Set Computers (CISC – Complex...). Хоча в цих процесорах не так багато інструкцій (що добре), інструкції ARM (а може й інших RISC-процесорів, я не знаю) мають багато різних призначень та комбінацій, що робить RISC-процесорами такими могутніми, якими вони є.

Реєстри

Я не знаю про інші процесори ARM, але у того, що використовується в GBA, 16 регістрів і на відміну від процесорів Intel (та інших), всі регістри можна спокійно використовувати (як правило). Реєстри такі:

r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15

Вау! Багато! Поясню по-порядку.

ro: Робіть що хочете!

r2 to r12: те саме

r13: На деяких ARM-системах r13 - це покажчик на стек (SP у процесорах INTEL). Я не впевнений, чи відіграє r13 ту ж роль у GBA, я можу тільки попередити, щоб ви були обережні з ним, коли працюєте зі стеком.

r14: Містить адресу повернення для процедур, що викликаються. Якщо ви їх не використовуєте, можете робити з ним все, що хочете.

r15: Program Counter і прапори, як і IP (Instruction Pointer в Intel). Він відрізняється від Intelівського регістру IP тим, що у вас є вільний доступ до нього, так само як і до будь-якого іншого регістру, але врахуйте, що його зміна призведе до того, що управління буде передано на іншу ділянку коду, а прапори зміняться .

Давайте проробимо невелику математичну вправу... 16 мінус 3 (зазвичай) дає нам 13 регістрів. Чи не так, це круто? Заспокойтесь.

Зараз ви можете запитати, чим насправді є регістри. Регістри – це спеціальні ділянки пам'яті, які є частиною процесора і не мають дійсної адреси, а відомі лише за своїми іменами. Регістри 32-х бітні. Майже все в будь-якій асемблерній мові використовує регістри, тому ви повинні знати їх так само добре, як своїх родичів.

Інструкції асемблера ARM

По-перше, я хочу почати з того, що на мою думку той, хто вигадав асемблер ARM, є генієм.

По-друге, я хочу уявити мого хорошого друга CMP. Скажіть йому привіт, і, можливо, тільки можливо, він стане і вашим другом. CMP розшифровується як CoMPare (порівняти). Ця інструкція може порівняти регістр та число, регістр та регістр або регістр та комірку пам'яті. Потім після порівняння CMP встановлює прапори статусу, які повідомляють вам результат порівняння. Як ви можете згадати, регістр r15 містить прапори. Вони повідомляють про результат порівняння. Інструкція CMP була спеціально створена для встановлення значення цих прапорів і більше.

Прапори можуть містити такі стани:

    EQ EQual / Рівне

    NE Not Equal / Не байдуже

    VS oVerflow Set / Встановлення переповнення

    VC oVerflow Clear / Очищення переповнення

    HI HIgher / Вище

    LS Lower or the Same / Нижче або те саме

    PL PLus / Плюс

    MI MINus / Мінус

    CS Carry Set / Встановлення перенесення

    CC Carry Clear / Очищення перенесення

    GE Greater than or Equal / Більше чи одно

    GT Greater Than / Більше

    LE Less than or Equal / Менше чи одно

    LT Less Than / Менше

    Z is Zero / Нуль

    NZ is not Zero / Не нуль

Ці стани відіграють важливу роль в асемблері ARM.

ПРИМІТКА: прапори лише зберігають умови (Рівне, Менше тощо). Більше вони нічим не важливі.

Суфікси умов

Ви вже побачили інструкцію B (Branch). Інструкція B робить те, що називається безумовним переходом (як GoTo у Basic або JMP в асемблері INTEL). Але вона може мати суфікс (один з перерахованих вище), тоді вона перевіряє, чи відповідає йому стан прапорів. Якщо ні, інструкція переходу просто не виконується. Тому, якщо ви хочете перевірити, чи регістр r0 дорівнює регістру r4, а потім перейти до мітки під назвою label34, то вам необхідно написати наступний код:

    CMP r0, r4; Коментарі в асемблері йдуть після крапки з комою (;)

    BEQ label34; B – інструкція переходу, а EQ – суфікс, що означає

    ; "Якщо Рівно"

ПРИМІТКА: У Goldroad Assembler мітки не потрібно супроводжувати (і крім імені мітки в рядку нічого не повинно бути.

ПРИМІТКА II: Писати CMP та BEQ великими літерамине обов'язково, це просто для того, щоб вам було зрозуміліше.

Тепер ви знаєте, як робити перехід в залежності від стану прапорів, але що ви не знаєте, так це те, що ви можете робити в залежності від стану прапорів все, що завгодно, просто додайте до будь-якої інструкції потрібний суфікс!

Також вам не потрібно використовувати CMP для встановлення стану прапорів. Якщо ви бажаєте, щоб, наприклад, інструкція SUB (Subtract) встановлювала прапори, додайте суфікс "S" до інструкції (розшифровується як "Set flags"). Це корисно, якщо ви не хочете встановлювати стан прапорів зайвою інструкцією CMP, тому зробити це і зробити перехід, якщо результат дорівнював нулю, можна наступним чином:

    SUBS r0, r1, 0x0FF; Встановлює прапори згідно з результатом виконання

    ; інструкції

    ldrZ r0, = 0x0FFFF; Завантажить в регістр r0 0x0FFFF тільки якщо стан

    прапорів і Нулю.

Огляд

Сьогодні ми вивчили (ще трохи) про регістри. Ми також дізналися про гнучкість інструкцій ARM, які можуть виконуватись (або не виконуватись) залежно від стану прапорів. Ми дізналися багато сьогодні.

Завтра ІІ використовуємо придбане сьогодні знання асемблера ARM для того, щоб вивести картинку на екран GBA.

Щось неможливе є таким лише до того часу, поки воно стає можливим /Jean-Luc Picard, Capt. , USS Enterprise/. Mike H, пров. Aquila

Спочатку ARMдосить незвичний асемблер (якщо переучуватися з x86, MCS51або AVR). Але в нього досить проста та логічна організація, тому засвоюється швидко.

Документації російською по асемблеру зовсім мало. Можу порадити зайти на 2 посилання (може, Ви знайдете більше, і підкажете мені? Буду вдячний):
Архітектура та система команд RISС-процесорів сімейства ARM - http://www.gaw.ru/html.cgi/txt/doc/micros/arm/index.htm
Осягаємо асемблер ARM, з циклу статей GBA ASM, автор Mike H, пров. Aquila - http://wasm.ru/series.php?sid=21.

Останнє посилання мені дуже допомогло, розвіяло туман =). Друге, що добре може допомогти - це, як не дивно, C-компілятор IAR Embedded Workbench for ARM(Далі просто IAR EW ARM). Справа в тому, що він з давніх часів вміє (як і всі компілятори, що поважають себе, втім) компілювати C-код в код асемблера, який, у свою чергу, так само легко компілюється асемблером IAR в об'єктний код. Тому немає нічого кращого написати найпростішу функцію на C, скомпілювати її в асемблер, і одразу стане зрозуміло, яка команда асемблера що робить, як передаються аргументи і як повертається результат. Вбиваєте відразу двох зайців - навчаєтесь асемблеру і заразом отримуєте інформацію, як інтергрувати асемблерний код у проект на C. Я тренувався на функції підрахунку CRC16, і в результаті отримав її повноцінну версіюна асемблері.

Ось вихідна функція на C (u16 означає unsigned short, u32 - unsigned int, u8 - unsigned char):
// файл crc16.c
u16 CRC16 (void* databuf, u32 size)
{
u16 tmpWord, crc16, idx;
u8 bitCnt;
#define CRC_POLY 0x1021;

Crc16 = 0;
idx=0;
while (size! = 0)
{
/* складемо xor старший байт crc16 і вхідний байт */
tmpWord = (crc16>>8) ^ (*(((u8*)databuf)+idx));
/* результат запишемо в старший байт crc16 */
tmpWord<<= 8;
crc16 = tmpWord + (0x00FF & crc16);
for (bitCnt=8;bitCnt!=0;bitCnt--)
{
/* перевіримо старший розряд акумулятора CRC */
if (crc16 & 0x8000)
{
crc16<<= 1;
crc16 ^= CRC_POLY;
}
else
crc16<<= 1;
}
idx++;
size--;
}
return crc16;
}

Змусити генерувати код асемблера IAR EW ARM дуже просто. У опціях файлу crc16.c (доданого до проекту) поставив галку Override inherited settings, а потім на закладці List поставив 3 галки - Output assembler file, Include sourceі Include call frame information(хоча останню галку, напевно, можна не ставити – вона генерує купу непотрібних CFI-Директив). Після компіляції вийшов файл папка_проекту \ewp\at91sam7x256_sram\List\crc16.s. Цей файл також легко можна додати в проект, як і файл C (він буде нормально компілюватися).

Звичайно, коли я підсунув C-компілятор необрізаний варіант C-коду, то він мені видав такий лістинг асемблера, що я нічого в ньому не зрозумів. Але коли викинув із функції всі C-оператори, окрім одного, стало зрозуміліше. Потім крок за кроком додавав C-оператори, і ось у результаті вийшло:

; файл crc16.s
NAME crc16
PUBLIC CRC16

CRC_POLY EQU 0x1021

SECTION `.text`:CODE:NOROOT(2)
ARM

// u16 CRC16 (void* databuf, u32 size)
;R0 - результат повернення, CRC16
;R1 - параметр size
R2 - параметр databuf (він був при вході в R0)
;R3, R12 - тимчасові регістри

CRC16:
PUSH (R3, R12); методом тику з'ясував, що R3 і R13 зберігати
; не обов'язково. Але вирішив зберегти на всякий
; випадок.
MOVS R2,R0 ;тепер R2==databuf
MOV R3, #+0
MOVS R0, R3; crc16 = 0
CRC16_LOOP:
CMP R1, # + 0; всі байти обробили (size = = 0)?
BEQ CRC16_RETURN ;якщо так, то вихід
LSR R3, R0, # +8; R3 = crc16>>8
LDRB R12, ;R12 = *databuf
EOR R3, R3, R12; R3 = * databuf ^ HIGH (crc16)
LSL R3, R3, # +8; R3<<= 8 (tmpWord <<= 8)
AND R0, R0, #+255; crc16 &= 0x00FF
ADD R0, R0, R3; crc16 = tmpWord + (0x00FF & crc16)
MOV R12, # +8; bitCnt = 8
CRC16_BIT_LOOP:
BEQ CRC16_NEXT_BYTE ;bitCnt == 0?
TST R0,#0x8000 ;Ще в повному обсязі біти оброблені.
BEQ CRC16_BIT15ZERO ;Перевіряємо старший біт crc16.
LSL R0, R0, # +1; crc16<<= 1
MOV R3, # + (LOW (CRC_POLY)); crc16 ^ = CRC_POLY
ORR R3, R3, # + (HIGH (CRC_POLY)<< 8) ;
EOR R0, R3, R0;
B CRC16_NEXT_BIT

CRC16_BIT15ZERO:
LSL R0, R0, # +1; crc16<<= 1
CRC16_NEXT_BIT:
SUBS R12, R12, # +1; bitCnt--
B CRC16_BIT_LOOP;

CRC16_NEXT_BYTE:
ADD R2,R2,#+1;databuf++
SUBS R1,R1,#+1 ;size--
B CRC16_LOOP ;цикл по всіх байтах

CRC16_RETURN:
POP (R3, R12); відновлюємо регістри
BX LR ;вихід із підпрограми, R0==crc16

Компілятор C від IAR робить напрочуд хороший код. Мені зовсім мало вдалося його оптимізувати. Викинув лише зайвий тимчасовий регістр, який хотів використати компілятор (він чомусь взяв як зайвий тимчасовий регістр LR, хоча R3 і R12 було достатньо), а також прибрав пару зайвих команд, які перевіряють лічильники та виставляють прапори (просто додавши суфікс S до потрібних командам).

Отже, ми створили новий проект, виконали основні налаштування, створили та підключили до проекту файл, у якому хочемо написати на асемблері якусь простеньку програму.

Що далі? Далі, власне, можна писати програму, використовуючи набір команд thumb-2, що підтримується ядром Cortex-M3. Список та опис підтримуваних команд можна переглянути в документі під назвою Cortex-M3 Generic User Guide(глава The Cortex-M3 Instruction Set), який можна знайти на вкладці Books в менеджері проекту, в Keil uVision 5. Детально про команди thumb-2 буде написано в одній з наступних частин цієї статті, а поки що поговоримо про програми для STM32 загалом.

Як і будь-яка інша програма на асемблері, програма для STM32 складається з команд і псевдокоманд, які будуть трансльовані безпосередньо в машинні коди, а також різних директив, які в машинні коди не транслюються, а використовуються в службових цілях (розмітка програми, присвоєння константам символьних імен і т.д.)

Наприклад, розбити програму на окремі секції дозволяє спеціальна директива. AREA. Вона має наступний синтаксис: AREA Section_Name (, type) (, attr) …, де:

  1. Section_name- Ім'я секції.
  2. type- Тип секції. Для секції, що містить дані, потрібно вказувати тип DATA, а для секції, що містить команди, — тип CODE.
  3. attr- Додаткові атрибути. Наприклад, атрибути readonly або readwrite вказують в якій пам'яті повинна розміщуватися секція, атрибут align=0..31 вказує яким чином секція повинна бути вирівняна в пам'яті, атрибут noinit використовується для виділення областей пам'яті, які не потрібно ініціалізувати або ініціалізуються нулями (при використанні цього атрибуту можна не вказувати тип секції, оскільки він може використовуватися тільки для даних секцій).

Директива EQUнапевно всім добре знайома, оскільки зустрічається в будь-якому асемблері і призначена для присвоєння символьних імен різним константам, осередкам пам'яті і т.д. Вона має наступний синтаксис: Name EQU numberі повідомляє компілятору, що всі символьні позначення, що зустрічаються Nameпотрібно замінювати на число number. Скажімо, якщо як numberвикористовувати адресу комірки пам'яті, то надалі до цієї комірки можна буде звертатися не за адресою, а використовуючи еквівалентне символьне позначення ( Name).

Директива GET filenameвставляє у програму текст із файлу з ім'ям filename. Це аналог директиви include в асемблері для AVR. Її можна використовувати, наприклад, щоб винести в окремий файл директиви присвоєння символьних імен різним регістрам. Тобто ми виносимо всі присвоєння імен до окремого файлу, а потім, щоб у програмі можна було користуватися цими символьними іменами, просто включаємо цей файл до нашої програми директивою GET.

Зрозуміло, крім перерахованих вище є ще купа різних директив, повний список яких можна знайти в розділі Directives Referenceдокумента Assembler User Guide, який можна знайти в Keil uVision 5 наступним шляхом: вкладка Booksменеджера проектів -> Tools User’s Guide -> Complete User’s Guide Selection -> Assembler User Guide.

Більшість команд, псевдокоманд та директив у програмі мають наступний синтаксис:

(label) SYMBOL (expr) (, expr) (, expr) (; коментар)

(label) - мітка. Вона потрібна для того, щоб можна було визначити адресу наступної команди. Мітка є необов'язковим елементом і використовується лише коли необхідно дізнатися адресу команди (наприклад, щоб перейти на цю команду). Перед міткою не повинно бути пробілів (тобто вона повинна починатися з першої позиції рядка), крім того, ім'я мітки може починатися тільки з літери.

SYMBOL – команда, псевдокоманда чи директива. Команда, на відміну від мітки, навпаки, повинна мати певний відступ від початку рядка, навіть якщо перед нею немає мітки.

(expr) (, expr) (, expr) - операнди (регістри, константи ...)

; - Розділювач. Весь текст у рядку після цього роздільника сприймається як коментар.

Ну а тепер, як і обіцяв, найпростіша програма:

AREA START, CODE, READONLY dcd 0x20000400 dcd Program_start ENTRY Program_start b Program_start END

AREA START, CODE, READONLY dcd 0x20000400 dcd Program_start ENTRY Program_start b Program_start END

У цій програмі у нас лише одна секція, яка називається START. Ця секція розміщується у flash-пам'яті (оскільки для неї використаний атрибут readonly).

Перші 4 байти цієї секції містять адресу вершини стека (у нашому випадку 0x20000400), а другі 4 байти - адреса точки входу (початок коду, що виконується). Далі слідує сам код. У найпростішому прикладі виконуваний код складається з однієї єдиної команди безумовного переходу на мітку Program_start, тобто знову виконання цієї команди.

Оскільки секція у флеші всього одна, то в scatter-файлі для нашої програми як First_Section_Name потрібно буде вказати саме її ім'я (тобто START).

В даному випадку у нас перемішані дані та команди. Адреса вершини стека та адреса точки входу (дані) записані за допомогою директив dcd прямо у секції коду. Так писати, звичайно, можна, але не дуже красиво. Особливо, якщо ми будемо описувати всю таблицю переривань і винятків (яка буде досить довгою), а не тільки вектор скидання. Набагато красивіше не захаращувати код зайвими даними, а помістити таблицю векторів переривань в окрему секцію, а ще краще - в окремий файл. Аналогічно в окремій секції або навіть файлі можна розмістити і ініціалізацію стека. Ми, наприклад, розмістимо все в окремих секціях:

AREA STACK, NOINIT, READWRITE SPACE 0x400; пропускаємо 400 байт Stack_top; та ставимо мітку AREA RESET, DATA, READONLY dcd Stack_top; адреса мітки Stack_top dcd Program_start; адреса теги Program_start AREA PROGRAM, CODE, READONLY ENTRY ; точка входу (початок виконуваного коду) Program_start; мітка початку програми b Program_start END

Ну ось, та сама програма (яка як і раніше не робить ніфіга корисного), але тепер виглядає набагато наочніше. У scatter-файлі для цієї програми потрібно вказати як First_Section_Name ім'я RESET, щоб ця секція розташовувалась у flash-пам'яті першою.

У цьому розділі описано набори інструкцій процесора ARM7TDMI.

4.1 Короткий опис формату

У цьому розділі наведено короткий опис наборів інструкцій ARM та Thumb.

Ключ до таблиць наборів інструкцій представлено таблиці 1.1.

Процесор ARM7TDMI виконаний з урахуванням архітектури ARMv4T. Більш повний опис обох наборів інструкцій наведено в "ARM Architecture Reference Manual".

Таблиця 1.1. Ключ до таблиць

Формати набору інструкцій ARM показані малюнку 1.5.

Більш детальна інформація щодо форматів набору інструкцій ARM наведена у "ARM Architectural Reference Manual".

Малюнок 1.5. Формати набору інструкцій ARM

Деякі коди інструкцій не визначені, але не викликають пошуку невизначених інструкцій, наприклад, інструкція множення з бітом 6 зміненим до 1. Забороняється використовувати такі інструкції, т.к. у майбутньому їхня дія може бути змінена. Результат виконання даних кодів інструкцій у складі процесора ARM7TDMI непередбачуваний.

4.2 Короткий опис інструкцій ARM

Набір інструкцій ARM наведено в таблиці 1.2.

Таблиця 1.2. Короткий опис інструкцій ARM

Операції Синтаксис Асемблера
Пересилання Пересилання MOV (cond)(S) Rd,
Пересилання NOT MVN (cond)(S) Rd,
Пересилання SPSR в регістр MRS (cond) Rd, SPSR
Пересилання CPSR у регістр MRS (cond) Rd, CPSR
Пересилання регістру SPSR MSR (cond) SPSR (field), Rm
Пересилання CPSR MSR (cond) CPSR (field), Rm
Пересилання константи у прапори SPSR MSR (cond) SPSR_f, #32bit_Imm
Пересилання константи у прапори CPSR MSR (cond) CPSR_f, #32bit_Imm
Арифметичні Додавання ADD (cond) (S) Rd, Rn,
Додавання з перенесенням ADC (cond)(S) Rd, Rn,
Віднімання SUB (cond)(S) Rd, Rn,
Віднімання з перенесенням SBC (cond)(S) Rd, Rn,
Віднімання зворотного віднімання RSB (cond)(S) Rd, Rn,
Віднімання зворотного віднімання з перенесенням RSC (cond)(S) Rd, Rn,
множення MUL (cond)(S) Rd, Rm, Rs
Множення-накопичення MLA (cond)(S) Rd, Rm, Rs, Rn
Умноження довгих беззнакових чисел UMULL
Множення - беззнакове накопичення довгих значень UMLAL (cond)(S) RdLo, RdHi, Rm, Rs
Примноження знакових довгих SMULL (cond)(S) RdLo, RdHi, Rm, Rs
Множення - знакове накопичення довгих значень SMLAL (cond)(S) RdLo, RdHi, Rm, Rs
Порівняння CMP (cond) Rd,
Порівняння негативне CMN (cond) Rd,
Логічні Перевірка TST (cond) Rn,
Перевірка на еквівалентність TEQ (cond) Rn,
Лог. І AND (cond)(S) Rd, Rn,
Викл. АБО EOR (cond)(S) Rd, Rn,
ORR ORR (cond)(S) Rd, Rn,
Скидання біта BIC (cond)(S) Rd, Rn, >
Перехід Перехід (cond) label
Перехід за посиланням (cond) label
Перехід та зміна набору інструкцій (cond) Rn
Читання слова LDR (cond) Rd,
LDR (cond)T Rd,
байта LDR (cond)B Rd,
LDR (cond)BT Rd,
байта зі знаком LDR (cond) SB Rd,
півслова LDR (cond) H Rd,
півслова зі знаком LDR (cond)SH Rd,
операції з кількома блоками даних -
  • з попереднім інкрементом
  • LDM (cond) IB Rd (, !} {^}
  • з наступним інкрементом
  • LDM (cond) IA Rd (, !} {^}
  • з попереднім декрементом
  • LDM (cond) DB Rd (, !} {^}
  • з наступним декрементом
  • LDM (cond)DA Rd(, !} {^}
  • операція над стеком
  • LDM (cond) Rd(, !}
  • операція над стеком та відновлення CPSR
  • LDM (cond) Rd(, !} ^
    операція над стеком з регістрами користувача LDM (cond) Rd(, !} ^
    Запис слова STR (cond) Rd,
    слова з перевагою режиму користувача STR (cond)T Rd,
    байта STR (cond) B Rd,
    байта з перевагою режиму користувача STR (cond)BT Rd,
    півслова STR (cond) H Rd,
    операції над кількома блоками даних -
  • з попереднім інкрементом
  • STM (cond) IB Rd (, !} {^}
  • з наступним інкрементом
  • STM (cond) IA Rd (, !} {^}
  • з попереднім декрементом
  • STM (cond) DB Rd (, !} {^}
    o з наступним декрементом STM (cond)DA Rd(, !} {^}
  • операція над стеком
  • STM (cond) Rd(, !}
  • операція над стеком з регістрами користувача
  • STM (cond) Rd(, !} ^
    Обмін слів SWP (cond) Rd, Rm,
    байт SWP (cond) B Rd, Rm,
    Співпроцесор Операція над даними CDP (cond) p , , CRd, CRn, CRm,
    Пересилання в ARM-реєстр із співпроцесора MRC (cond) p , , Rd, CRn, CRm,
    Пересилання в співпроцесор з ARM-реєстру MCR (cond) p , , Rd, CRn, CRm,
    Читання LDC (cond) p , CRd,
    Запис STC (cond) p , CRd,
    Програмне переривання SWI 24bit_Imm

    Докладно ознайомитися із системою команд у режимі ARM можна.

    Режими адресації

    Режими адресації - процедури, що використовуються різними інструкціями для створення значень, що використовуються інструкціями. Процесор ARM7TDMI підтримує 5 режимів адресації:

    • Режим 1 – Зсувні операнди для інструкцій обробки даних.
    • Режим 2 - Читання та запис слова або беззнакового байта.
    • Режим 3 - Читання та запис напівслова або завантаження знакового байта.
    • Режим 4 - Численні читання та запис.
    • Режим 5 - Читання та запис співпроцесора.

    Режими адресації із зазначенням їх типів та мнемонічних кодів представлені в таблиці 1.3.

    Таблиця 1.3. Режими адресації

    Режим адресації Тип або режим адресації Мнемонічний код або тип стеку
    Режим 2 Константа усунення
    Регістр усунення
    Масштабний регістр усунення
    Попереднє індексоване зміщення -
    Константа !
    Реєстр !
    Масштабний регістр !
    !
    !
    !
    !
    -
    Константа , #+/-12bit_Offset
    Реєстр , +/-Rm
    Масштабний регістр
    Режим 2, привілейований Константа усунення
    Регістр усунення
    Масштабний регістр усунення
    Зміщення з наступним індексуванням -
    Константа , #+/-12bit_Offset
    Реєстр , +/-Rm
    Масштабний регістр , +/-Rm, LSL #5bit_shift_imm
    , +/-Rm, LSR #5bit_shift_imm
    , +/-Rm, ASR #5bit_shift_imm
    , +/-Rm, ROR #5bit_shift_imm
    Режим 3, > Константа усунення
    !
    Наступне індексування , #+/-8bit_Offset
    Реєстр
    Попереднє індексування !
    Наступне індексування , +/-Rm
    Режим 4, читання IA, наступний інкремент FD, full descending
    ED, empty descending
    DA, наступний декремент FA, full ascending
    DB попередній декремент EA, empty ascending
    Режим 4, запис IA, наступний інкремент FD, full descending
    IB, попередній інкремент ED, empty descending
    DA, наступний декремент FA, full ascending
    DB попередній декремент EA, empty ascending
    Режим 5, передача даних співпроцесора Константа усунення
    Попереднє індексування !
    Наступне індексування , #+/-(8bit_Offset*4)

    Операнд 2

    Операнд є частиною інструкції, яка посилається на дані або периферійний пристрій. Операнди 2 представлені у таблиці 1.4.

    Таблиця 1.4. Операнд 2

    Поля представлені у таблиці 1.5.

    Таблиця 1.5. Поля

    Поля умов

    Поля умов подано у таблиці 1.6.

    Таблиця 1.6. Поля умов

    Тип поля Суфікс Опис Умова
    Умова (cond) EQ Рівно Z=1
    NE Не дорівнює Z=0
    CS Беззнакове більше чи одно C=1
    CC Беззнакове менше C=0
    MI Негативне N=1
    PL Позитивне або нуль N=0
    VS Переповнення V=1
    VC Немає переповнення V=0
    HI Беззнакове більше C=1, Z=0
    LS Беззнакове менше чи одно C=0, Z=1
    GE Більше або дорівнює N=V (N=V=1 або N=V=0)
    LT Менше NV (N=1 та V=0) або (N=0 та V=1)
    GT Більше Z=0, N=V (N=V=1 або N=V=0)
    LE Менше або дорівнює Z=0 або NV (N=1 та V=0) або (N=0 та V=1)
    AL Завжди правдивий прапори ігноруються

    4.3 Короткий опис набору інструкцій Thumb

    Формати набору інструкцій Thumb показані малюнку 1.6. Докладніше про формати наборів інструкцій ARM наведено "ARM Architectural Reference Manual".


    Малюнок 1.6. Формати набору інструкцій Thumb

    Набір інструкцій Thumb наведено в таблиці 1.7.

    Таблиця 1.7. Короткий опис набору інструкцій Thumb

    Операція Синтаксис Асемблера
    Пересилання (копіювання) константи MOV Rd #8bit_Imm
    старшого до молодшого MOV Rd, Hs
    молодшого до старшого MOV Hd, Rs
    старшого до старшого MOV Hd, Hs
    Арифметичні додавання ADD Rd, Rs, #3bit_Imm
    додати молодший до молодшого ADD Rd, Rs, Rn
    додати старший до молодшого ADD Rd, Hs
    додати молодший до старшого ADD Hd, Rs
    додати старший до старшого ADD Hd, Hs
    додавання з константою ADD Rd #8bit_Imm
    додати значення до SP ADD SP, #7bit_Imm ADD SP, #-7bit_Imm
    додавання з урахуванням перенесення ADC Rd, Rs
    віднімання SUB Rd, Rs, Rn SUB Rd, Rs #3bit_Imm
    віднімання константи SUB Rd #8bit_Imm
    віднімання з перенесенням SBC Rd, Rs
    інверсія знаку NEG Rd, Rs
    множення MUL Rd, Rs
    порівняти молодший із молодшим CMP Rd, Rs
    порівняти молодший та старший CMP Rd, Hs
    порівняти старший та молодший CMP Hd, Rs
    порівняти старший та старший CMP Hd, Hs
    порівняти негативні CMN Rd, Rs
    порівняти з константою CMP Rd #8bit_Imm
    Логічні І AND Rd, Rs
    Викл. АБО EOR Rd, Rs
    АБО ORR Rd, Rs
    Скидання біта BIC Rd, Rs
    Пересилання NOT MVN Rd, Rs
    Тестування біт TST Rd, Rs
    Зсув/обертання Логічний зсув вліво LSL Rd, Rs, #5bit_shift_imm LSL Rd, Rs
    Логічний зсув праворуч LSR Rd, Rs, #5bit_shift_imm LSR Rd, Rs
    Арифметичний зсув праворуч ASR Rd, Rs, #5bit_shift_imm ASR Rd, Rs
    Обертання вправо ROR Rd, Rs
    Перехід умовні переходи -
    BEQ label
    BNE label
    BCS label
    BCC label
    BMI label
    BPL label
    BVS label
    BVC label
  • C=1, Z=0
  • BHI label
  • C=0, Z=1
  • BLS label
  • N=1, V=1 або N=0, V=0
  • BGE label
  • N=1, V=0 або N=0, V=1
  • BLT label
  • Z=0 і ((N або V=1) або (N або V=0))
  • BGT label
  • Z=1 або ((N=1 або V=0) або (N=0 та V=1))
  • BLE label
    Безумовний перехід B label
    Довгий перехід за посиланням BL label
    Опціональна зміна стану -
  • за адресою у мл. регістрі
  • BX Rs
  • за адресою ст. регістрі
  • BX Hs
    Читання з константою усунення -
  • слова
  • LDR Rd,
  • півслова
  • LDRH Rd,
  • байта
  • LDRB Rd,
    з регістром зміщення -
  • слова
  • LDR Rd,
  • півслова
  • LDRH Rd,
  • знакового півслова
  • LDRSH Rd,
    LDRB Rd,
  • знакового байта
  • LDRSB Rd,
    щодо лічильника програми PC LDR Rd,
    щодо покажчика стека SP LDR Rd,
    Адреса -
  • за допомогою PC
  • ADD Rd, PC, #10bit_Offset
  • за допомогою SP
  • ADD Rd, SP, #10bit_Offset
    Множинне читання LDMIA Rb!,
    Запис з константою усунення -
  • слова
  • STR Rd,
  • півслова
  • STRH Rd,
  • байта
  • STRB Rd,
    з регістром зміщення -
  • слова
  • STR Rd,
  • півслова
  • STRH Rd,
  • байта
  • STRB Rd,
    щодо SP STR Rd,
    Множинний запис STMIA Rb!,
    Приміщення/ витяг з стека Помістити регістри у стек PUSH
    Помістити LR та регістри у стек PUSH
    Витягти регістри зі стека POP
    Витягти регістри та PC зі стека POP
    Програмне переривання - SWI 8bit_Imm

     

     

    Це цікаво: