Перейти к содержанию

Система команд Thumb-2 для Cortex-M

Справочник по системе команд Thumb-2 ядер Cortex-M: все команды Cortex-M3 по группам с флагами, правила второго операнда и констант, условное выполнение и блок IT, дальность переходов, выравнивание, деление и насыщение, что добавляет Cortex-M4 и чего нет у Cortex-M0. По программным руководствам ST PM0056, PM0214 и PM0215, с примерами на ассемблере.

Обновлено Редакция GAW

Thumb-2 — система команд ядер ARM Cortex-M. В ней 16-битные команды классического набора Thumb смешаны с 32-битными: короткие дают плотный код, длинные — доступ ко всем регистрам, большие константы, деление, битовые поля и условное выполнение. Ядра Cortex-M выполняют только Thumb-2, состояния ARM с 32-битными командами, как у ARM7TDMI, у них нет. Ассемблер сам выбирает длину каждой команды, а программист пишет на едином языке ассемблера UAL.

Полный набор — у ядер ARMv7-M: Cortex-M3, M4 и M7. Cortex-M4 добавляет к нему команды цифровой обработки сигналов и, если есть FPU, команды плавающей точки (PM0214). Cortex-M0 и M0+ (ARMv6-M) выполняют подмножество — около полусотни команд, почти все 16-битные (PM0215). Здесь — команды Cortex-M3 по программному руководству ST PM0056 с пометками, что есть у других ядер. Устройство ядра, регистры и такты команд разобраны в статье об архитектуре Cortex-M3, сравнение ядер — на странице ARM Cortex-M.

Коротко

  • Регистры: R0–R12, SP (R13), LR (R14), PC (R15); большинство 16-битных команд работает только с R0–R7.
  • Флаги N, Z, C, V и Q в APSR; команды обработки данных меняют их только с суффиксом S.
  • Условное выполнение: условные переходы B<cond> или блок IT до четырёх команд.
  • Константы: 8-битное значение со сдвигом или шаблоны 0x00XY00XY, 0xXY00XY00, 0xXYXYXYXY; любое 32-битное число — MOVW и MOVT.
  • Переходы: B и BL — ±16 МБ, условный B вне блока IT — ±1 МБ, CBZ и CBNZ — вперёд на 4–130 байт.
  • Деление SDIV и UDIV — с округлением к нулю, деление на ноль даёт 0, если не включена ловушка DIV_0_TRP.

Регистры и операнды

Команды обработки данных записываются в виде op{S}{cond} Rd, Rn, Operand2: Rd — приёмник, Rn — первый операнд, Operand2 — гибкий второй операнд (PM0056, раздел 3.3.3). Если Rd не указан, приёмником служит Rn. Второй операнд может быть:

  • константой #constant: 8-битное значение, сдвинутое влево на любое число бит в пределах 32-битного слова, или число вида 0x00XY00XY, 0xXY00XY00, 0xXYXYXYXY, где X и Y — шестнадцатеричные цифры. Например, 0x3FC (0xFF, сдвинутое на 2 бита) и 0x00AB00AB допустимы, а 0x101 — нет. Если константа недопустима, ассемблер может подставить равносильную команду: CMP Rd, #0xFFFFFFFE превращается в CMN Rd, #0x2;
  • регистром со сдвигом Rm {, shift}: ASR #n и LSR #n — сдвиг вправо на 1–32 бита, LSL #n — влево на 1–31, ROR #n — вращение на 1–31, RRX — вращение на один бит через флаг C. Сдвиг применяется к значению, а сам Rm не меняется.

Шире константы у немногих команд: ADDW и SUBW принимают 12-битное число 0–4095, MOVW — 16-битное, а MOVT записывает 16 бит в старшую половину регистра, не трогая младшую. Пара MOVW и MOVT загружает любое 32-битное значение двумя командами (PM0056, раздел 3.5.7).

Логические команды с суффиксом S (ANDS, ORRS, EORS, BICS, ORNS, MOVS, MVNS, TST, TEQ) с константой больше 255, полученной сдвигом 8-битного значения, меняют и флаг C; с другими константами C не меняется.

Флаги и условное выполнение

ФлагКогда устанавливается
Nрезультат отрицательный (бит 31 = 1)
Zрезультат равен нулю
Cперенос при сложении (результат ≥ 2³²), отсутствие заёма при вычитании, выдвинутый бит при сдвиге
Vпереполнение со знаком: результат сложения, вычитания или сравнения ≥ 2³¹ или меньше −2³¹
Qбыло насыщение в SSAT и USAT (у Cortex-M4 — и в командах DSP); «липкий» флаг, сбрасывается только программой

Большинство команд обновляют флаги только с суффиксом S; CMP, CMN, TST и TEQ меняют их всегда и никуда не пишут результат (PM0056, раздел 3.3.7). Условие проверяется суффиксом:

СуффиксФлагиЗначение
EQ / NEZ = 1 / Z = 0равно / не равно
CS (HS) / CC (LO)C = 1 / C = 0без знака ≥ / без знака <
MI / PLN = 1 / N = 0отрицательное / неотрицательное
VS / VCV = 1 / V = 0переполнение / нет переполнения
HI / LSC = 1 и Z = 0 / C = 0 или Z = 1без знака > / без знака ≤
GE / LTN = V / N ≠ Vсо знаком ≥ / со знаком <
GT / LEZ = 0 и N = V / Z = 1 или N ≠ Vсо знаком > / со знаком ≤
ALлюбыевсегда, по умолчанию

Таблица — по PM0056 (таблица 23). Условными без дополнительных команд бывают только переходы B<cond>. Остальные команды становятся условными внутри блока IT (If-Then): команда IT{x{y{z}}} cond делает условными до четырёх следующих команд, где x, y и z — T (условие cond) или E (обратное условие) для второй, третьей и четвёртой команды (PM0056, раздел 3.8.7). Внутри блока нельзя использовать IT, CBZ, CBNZ, CPSID и CPSIE, а команда, меняющая PC, может стоять только последней. Многие ассемблеры вставляют IT сами, если встречают условную команду вне блока.

Модуль числа и условное присваивание — примеры из PM0056:

    MOVS    R0, R1          @ R0 = R1, set N and Z
    IT      MI              @ the next instruction runs if negative
    RSBMI   R0, R1, #0      @ R0 = -R1

    CMP     R0, R1          @ signed compare
    ITT     GT              @ two instructions on "greater than"
    CMPGT   R2, R3          @ compare the second pair only if R0 > R1
    MOVGT   R4, R5          @ R4 = R5 if both pairs are "greater than"

Ширина команды: .N и .W

Многие команды имеют и 16-битную, и 32-битную кодировку, и ассемблер выбирает короткую, если позволяют регистры и константа. Суффикс .W заставляет выбрать 32-битную форму, .N — 16-битную; если её нет, ассемблер выдаёт ошибку (PM0056, раздел 3.3.8). .W бывает нужен для переходов на далёкие метки, где ассемблер не может сам выбрать длину: BCS.W label, ADDS.W R0, R0, R1.

Команды Cortex-M3 по группам

ГруппаКомандыФлагиCortex-M0
Сложение и вычитаниеADD, ADDW, ADC, SUB, SUBW, SBC, RSBN, Z, C, VADD, ADCS, SUB, SBCS; RSBS только с #0
ЛогикаAND, ORR, EOR, BIC, ORNN, Z, CANDS, ORRS, EORS, BICS; ORN нет
СдвигиASR, LSL, LSR, ROR, RRXN, Z, CASRS, LSLS, LSRS, RORS; RRX нет
Сравнение и проверкаCMP, CMN, TST, TEQN, Z, C, VCMP, CMN, TST; TEQ нет
ПересылкаMOV, MVN, MOVW, MOVTN, Z, C у MOV и MVNMOV, MOVS, MVNS; MOVW и MOVT нет
Перестановка битов и байтовREV, REV16, REVSH, RBIT, CLZ—REV, REV16, REVSH; RBIT и CLZ нет
Битовые поля и расширениеBFC, BFI, SBFX, UBFX, SXTB, SXTH, UXTB, UXTH—только SXTB, SXTH, UXTB, UXTH
УмножениеMUL, MLA, MLS, UMULL, UMLAL, SMULL, SMLALN, Z у MULSтолько MULS
ДелениеSDIV, UDIV—нет
НасыщениеSSAT, USATQнет
Загрузка и сохранениеLDR, LDRB, LDRH, LDRSB, LDRSH, LDRD, STR, STRB, STRH, STRD, формы с T для непривилегированного доступа, ADR—LDR, LDRB, LDRH, LDRSB, LDRSH, STR, STRB, STRH, ADR; LDRD, STRD нет
Множественная загрузка и стекLDM, LDMDB, STM, STMDB, PUSH, POP—LDM, STM, PUSH, POP
Монопольный доступLDREX, LDREXB, LDREXH, STREX, STREXB, STREXH, CLREX—нет
ПереходыB, BL, BX, BLX, CBZ, CBNZ, TBB, TBH, IT—B, BL, BX, BLX; CBZ, CBNZ, TBB, TBH и IT нет
УправлениеCPSID, CPSIE, MRS, MSR, DMB, DSB, ISB, SVC, BKPT, NOP, SEV, WFE, WFIN, Z, C, V у MSRвсе

Таблица собрана по PM0056 (таблица 20) и PM0215 (таблица 14). У Cortex-M0 почти все команды обработки данных работают только с регистрами R0–R7 и всегда меняют флаги (отсюда суффикс S в мнемониках), а условными бывают только переходы.

Некоторые команды недоступны из C напрямую, и CMSIS даёт для них встроенные функции: __enable_irq() и __disable_irq() (CPSIE I и CPSID I), __DSB(), __DMB(), __ISB(), __REV(), __REV16(), __REVSH(), __RBIT(), __SEV(), __WFE(), __WFI() (PM0056, таблица 21).

Переходы

КомандаДальность
B label±16 МБ
B<cond> label вне блока IT±1 МБ
B<cond> label внутри блока IT±16 МБ
BL label±16 МБ
BX Rm, BLX Rmлюбой адрес из регистра
CBZ, CBNZ Rn, labelвперёд на 4–130 байт, только R0–R7

Таблица — по PM0056 (разделы 3.8.5 и 3.8.6); для наибольшей дальности может понадобиться суффикс .W. У адреса перехода в BX и BLX бит 0 должен быть равен 1 — это признак состояния Thumb; с нулевым битом ядро вызывает отказ при выполнении следующей команды. Компоновщик ставит этот бит в адресах функций сам.

CBZ и CBNZ сравнивают регистр с нулём и переходят, не меняя флагов, — удобно для циклов и проверок указателей. TBB и TBH — переход по таблице смещений: TBB берёт байт, TBH — полуслово из таблицы по адресу Rn с индексом Rm, удваивает его и прибавляет к адресу за командой (PM0056, раздел 3.8.8). Так компилятор реализует плотные switch.

Загрузка, сохранение и выравнивание

Обращения к памяти идут только через LDR и STR в разных вариантах: со смещением-константой, с регистровым смещением со сдвигом, с пред- и постинкрементом базового регистра, относительно PC для констант в памяти. LDRD и STRD пересылают два слова, LDM и STM — список регистров, PUSH и POP — синонимы STMDB и LDMIA с адресом в SP и обновлением SP (PM0056, раздел 3.4.7).

Невыровненный доступ Cortex-M3 разрешает только командам LDR, LDRT, LDRH, LDRHT, LDRSH, LDRSHT, STR, STRT, STRH и STRHT. Остальные — LDRD, STRD, LDM, STM, PUSH, POP, LDREX и STREX — при невыровненном адресе вызывают отказ UsageFault. ARM рекомендует выравнивать данные: невыровненный доступ медленнее, а некоторые области памяти его не поддерживают; бит UNALIGN_TRP регистра SCB_CCR включает ловушку и на разрешённые невыровненные обращения (PM0056, раздел 3.3.5).

LDREX и STREX — монопольный доступ для семафоров и атомарных счётчиков: STREX записывает значение, только если с момента LDREX никто не обращался к этому адресу, и кладёт в регистр результата 0 при успехе и 1 при неудаче (PM0056, раздел 3.4.8). Адрес и размер данных у пары команд должны совпадать.

Умножение, деление и насыщение

MUL, MLA и MLS дают 32-битный результат, UMULL, SMULL, UMLAL и SMLAL — 64-битный в паре регистров. SDIV и UDIV делят 32-битные числа со знаком и без знака, результат округляется к нулю (PM0056, раздел 3.6.3). Деление на ноль по умолчанию возвращает 0, а если установить бит DIV_0_TRP регистра SCB_CCR, вызывает отказ UsageFault (PM0056, раздел 4.4.7). У Cortex-M0 аппаратного деления нет, и компилятор вызывает библиотечную функцию.

SSAT и USAT ограничивают значение n-битным диапазоном: SSAT — от −2ⁿ⁻¹ до 2ⁿ⁻¹ − 1 (n = 1…32), USAT — от 0 до 2ⁿ − 1 (n = 0…31), при необходимости после сдвига. Если значение пришлось ограничить, ставится флаг Q (PM0056, раздел 3.7.1). Так одним тактом решается задача «обрезать результат фильтра до 12 бит ЦАП»: USAT R0, #12, R0.

Битовые поля и перестановки

Битовые команды заменяют связки сдвигов и масок, которые на 8-битных микроконтроллерах занимают несколько команд:

КомандаДействиеНа C
UBFX Rd, Rn, #lsb, #widthизвлечь поле без знака(x >> lsb) & ((1u << width) - 1)
SBFX Rd, Rn, #lsb, #widthизвлечь поле со знаковым расширениемполе как знаковое число
BFI Rd, Rn, #lsb, #widthвставить младшие биты Rn в поле Rdd = (d & ~mask) | ((n << lsb) & mask)
BFC Rd, #lsb, #widthобнулить полеd &= ~mask
CLZ Rd, Rmчисло ведущих нулей31 − ⌊log₂ x⌋ для x > 0
RBIT Rd, Rnобратный порядок битов__RBIT(x)
REV Rd, Rnобратный порядок байтов слова__REV(x), смена порядка байтов

Компиляторы сами используют UBFX, BFI и CLZ для подходящих выражений и для __builtin_clz. Именно через RBIT и CLZ драйверы LL STM32 вычисляют номер вывода из маски — макрос POSITION_VAL, разобранный в статье о HAL, LL и регистрах.

Пример: записать 4-битное значение из R1 в биты 8–11 регистра R0 и прочитать их обратно.

    BFI     R0, R1, #8, #4      @ R0[11:8] = R1[3:0], other bits unchanged
    UBFX    R2, R0, #8, #4      @ R2 = R0[11:8]

Что добавляет Cortex-M4

Cortex-M4 выполняет все команды Cortex-M3 и добавляет две группы (PM0214, глава 3):

  • DSP и SIMD — операции над парами полуслов или четвёрками байтов в одном регистре: SADD16, SADD8, UADD16, UADD8, SSUB16, USUB8, их варианты с делением пополам (SHADD16, UHADD8) и с насыщением (QADD16, UQADD8), обмен половин SASX и SSAX; умножение полуслов с накоплением SMLA, SMLAW, SMLAD, SMLSD, SMLALD; старшая половина произведения SMMUL, SMMLA; насыщающее сложение QADD, QSUB, QDADD; упаковка PKHBT и PKHTB; расширение с прибавлением SXTAB, UXTAH; SSAT16 и USAT16. На них построены функции библиотеки CMSIS-DSP для фильтров и БПФ.
  • FPU одинарной точности (Cortex-M4F) — команды с префиксом V: VADD.F32, VSUB, VMUL, VDIV, VSQRT, VABS, VNEG, умножение с накоплением VMLA и VMLS и слитное VFMA и VFMS, сравнение VCMP, преобразования VCVT между целыми, фиксированной точкой и плавающей, пересылки VMOV, загрузка и сохранение VLDR, VSTR, VLDM, VSTM, VPUSH, VPOP, доступ к регистру состояния FPSCR через VMRS и VMSR.

Чего нет у Cortex-M0 и M0+

Cortex-M0 реализует архитектуру ARMv6-M — 16-битный Thumb с элементами Thumb-2 (PM0215). По сравнению с Cortex-M3 у него нет: блока IT и условного выполнения команд, кроме переходов; CBZ, CBNZ, TBB и TBH; аппаратного деления; MOVW и MOVT; битовых полей BFI, BFC, UBFX, SBFX; RBIT и CLZ; длинного умножения UMULL и SMULL; насыщения SSAT и USAT; монопольного доступа LDREX и STREX; LDRD и STRD; гибкого второго операнда со сдвигом и большинства форм с константами. Поэтому один и тот же код на C для Cortex-M0 получается длиннее и медленнее, а атомарные операции делают запретом прерываний.

Как читать ассемблер компилятора

Даже без программирования на ассемблере полезно уметь прочитать листинг: так видно, во что превратился код на C, где компилятор вызвал библиотечное деление, где появился лишний доступ к volatile. Листинг даёт arm-none-eabi-objdump -d или окно дизассемблера отладчика. Типичные признаки:

  • PUSH {R4-R7, LR} и POP {R4-R7, PC} — пролог и эпилог функции: сохранены регистры, которые функция обязана сохранить, и адрес возврата;
  • LDR R0, [PC, #offset] или LDR R0, =value — константа из пула литералов после функции;
  • BL __aeabi_uidiv — деление в библиотеке: на Cortex-M0 это норма, на Cortex-M3 — признак того, что код собран не для того ядра;
  • DSB и ISB после записи в регистры ядра — барьеры, которые вставили функции CMSIS.

Для перевода констант между системами счисления пригодится конвертер чисел.

Частые вопросы

Чем Thumb-2 отличается от Thumb?

Классический Thumb — набор 16-битных команд, который у ARM7TDMI работал рядом с 32-битным набором ARM и не давал доступа ко всем возможностям процессора. Thumb-2 дополняет 16-битные команды 32-битными: в одном потоке смешаны команды обеих длин, и 32-битные дают доступ ко всем регистрам, большие константы, деление, битовые поля и условное выполнение блоком IT. Ядра Cortex-M выполняют только Thumb-2 — переключаться в состояние ARM им не нужно.

Какие константы можно использовать в командах Cortex-M3?

Во втором операнде большинства команд обработки данных — 8-битное значение, сдвинутое влево на любое число бит в пределах 32-битного слова, а также числа вида 0x00XY00XY, 0xXY00XY00 и 0xXYXYXYXY (PM0056, раздел 3.3.3). ADDW и SUBW принимают 12-битную константу, MOVW — 16-битную. Любое 32-битное число загружается парой MOVW и MOVT или из памяти командой LDR.

Сколько команд можно сделать условными командой IT?

До четырёх: IT, ITT, ITE, ITTT, ITTE и так далее, где каждая буква после первой T — Then или Else для следующей команды. Внутри блока нельзя использовать IT, CBZ, CBNZ, CPSID и CPSIE, а переход или запись в PC может быть только последней командой блока (PM0056, раздел 3.8.7).

Что вернёт деление на ноль на Cortex-M3?

Ноль, если бит DIV_0_TRP регистра SCB_CCR сброшен — так по умолчанию. Если его установить, SDIV или UDIV с нулевым делителем вызывают отказ UsageFault (PM0056, раздел 4.4.7). Результат деления всегда округляется к нулю.

Источники

  1. STMicroelectronics. PM0056 Rev 7. STM32F10xxx/20xxx/21xxx/L1xxxx Cortex-M3 programming manual — 3.1 — таблица 20, все команды Cortex-M3 с флагами; 3.2 — встроенные функции CMSIS (таблица 21); 3.3.3 — второй операнд и константы; 3.3.4 — сдвиги; 3.3.5 — выравнивание; 3.3.7 — флаги и условия (таблица 23); 3.3.8 — суффиксы .N и .W; 3.5.7 — MOVT; 3.6.3 — SDIV и UDIV; 3.7.1 — SSAT и USAT; 3.8.5 — дальность переходов; 3.8.6 — CBZ и CBNZ; 3.8.7 — IT; 3.8.8 — TBB и TBH; 4.4.7 — DIV_0_TRP и UNALIGN_TRP
  2. STMicroelectronics. PM0214 Rev 10. STM32 Cortex-M4 MCUs and MPUs programming manual — 3.5–3.8 — команды DSP: SIMD-сложение и вычитание байтов и полуслов, умножение полуслов SMLA, SMLAD, SMMUL, насыщающая арифметика QADD, QSUB, упаковка PKHBT; 3.10 — команды FPU одинарной точности от VABS до VSQRT
  3. Arm. Run-time ABI for the Arm Architecture (RTABI32) и Procedure Call Standard (AAPCS32) — функции деления __aeabi_uidiv и __aeabi_idiv; регистры R4–R11, которые сохраняет вызываемая функция
  4. STMicroelectronics. PM0215 Rev 2. STM32F0 series Cortex-M0 programming manual — ARMv6-M на основе 16-битного Thumb с технологией Thumb-2; таблица 14 — все команды Cortex-M0

Нашли ошибку?