MicroPython:Основы/Язык MicroPython и его реализация/Ассемблерная вставка для архитектур Thumb2: различия между версиями

Материал из Онлайн справочника
Перейти к навигации Перейти к поиску

 
(не показаны 2 промежуточные версии этого же участника)
Строка 3: Строка 3:
 
{{Myagkij-редактор}}
 
{{Myagkij-редактор}}
  
 +
=Ассемблерная вставка для архитектур Thumb2<ref>[http://docs.micropython.org/en/latest/reference/asm_thumb2_index.html docs.micropython.org - Inline assembler for Thumb2 architectures]</ref>=
  
 +
Это руководство рассчитано на тех, кто уже более-менее знаком с ассемблерным программированием и ознакомился с [http://docs.micropython.org/en/latest/pyboard/tutorial/assembler.html#pyboard-tutorial-assembler этим руководством]. Подробное описание команд читайте в справочнике по архитектуре [[ARM v7-M]] (см. ниже). В ассемблерной вставке [[MicroPython]] поддерживается использование некоторых команд из набора ARM Thumb-2 – именно о них и пойдет речь ниже. Эти команды переделаны в [[Python-функции]], но их синтаксис максимально приближен к синтаксису из вышеупомянутого справочника по [[ARM v7-M]].
 +
 +
Команды оперируют 32-битными знаковыми целочисленными данными, за исключением случаев, когда указано обратное. Большинство поддерживаемых команд работают только на ''регистрах R0-R7'', но есть и команды, работающие на ''R8-R15'' (в этом случае об этом будет сказано в ее описании). Перед тем, как функция вернет результат, ''регистрам R8-R12'' должно быть возвращено их первоначальное значение. ''Регистры R13-R15'' – это, соответственно, регистр связи, указатель стека и счетчик программ.
 +
 +
== Условные обозначения и правила оформления кода ==
 +
 +
Где это возможно, поведение каждой команды описано на [[Python]]. Например:
 +
 +
<syntaxhighlight lang="python" enclose="div">
 +
add(Rd, Rn, Rm): Rd = Rn + Rm
 +
</syntaxhighlight>
 +
 +
Это позволяет продемонстрировать работу этих функций на примере [[Python]]-кода. В некоторых случаях это невозможно, потому что в [[Python]] не поддерживаются концепты вроде косвенной адресации. Псевдокод, используемый в таких ситуациях, описан на соответствующей странице.
 +
 +
== Типы команд ==
 +
 +
В разделах ниже содержится описание команд [[ARM Thumb-2]], поддерживаемых в [[MicroPython]]:
 +
# Команды перемещения данных
 +
# Команды чтения данных из регистра
 +
# Команды записи данных в регистр
 +
# Логические команды и команды сдвига
 +
# Арифметические команды
 +
# Команды сравнения
 +
# Команды условных переходов
 +
# Команды записи и чтения из стека
 +
# Прочие команды
 +
# Команды для чисел с плавающей точкой
 +
# Ассемблерные директивы
 +
 +
== Примеры использования ==
 +
 +
В этом разделе можно найти примеры и советы по использованию ассемблерного кода.
 +
# [http://docs.micropython.org/en/latest/reference/asm_thumb2_hints_tips.html Советы по использованию ассемблерных вставок]
 +
 +
==Справочные материалы==
 +
 +
* [http://docs.micropython.org/en/latest/pyboard/tutorial/assembler.html#pyboard-tutorial-assembler Руководство по использованию ассемблера]
 +
* [http://wiki.micropython.org/platforms/boards/pyboard/assembler Wiki с полезными советами]
 +
* [https://github.com/micropython/micropython/blob/master/py/emitinlinethumb.c Исходный код emitinlinethumb.c – для ассемблерных вставок в uPy]
 +
* [http://infocenter.arm.com/help/topic/com.arm.doc.qrc0001l/QRC0001_UAL.pdf Памятка по командам для ARM Thumb2]
 +
* [https://www.st.com/resource/en/reference_manual/DM00031020-.pdf Справочное руководство по RM0090]
 +
* Справочник по архитектуре [[ARM v7-M]] (можно найти на сайте ARM после простой регистрации; также есть на научных сайтах, но следите за тем, чтобы вам не попалась устаревшая версия).
 +
 +
==Команды перемещения данных ==
 +
 +
===Условные обозначения===
 +
 +
Под ''Rd'' и ''Rn'' подразумеваются ARM-регистры ''R0-R15''. Под ''immN'' подразумевается непосредственное значение с разрешением в ''N бит''. Эти инструкции влияют на флаги состояния.
 +
 +
===Перемещение данных в регистр===
 +
 +
Если используется непосредственное значение, оно будет дополнено нулями, чтобы ''«дотянуть»'' его до 32 бит. Таким образом, ''mov(R0, 0xff)'' запишет в ''R0'' значение ''«255»''.
 +
* mov(Rd, imm8): Rd = imm8
 +
* mov(Rd, Rn): Rd = Rn
 +
* movw(Rd, imm16): Rd = imm16
 +
* movt(Rd, imm16): Rd = (Rd & 0xffff) | (imm16 << 16)
 +
** Функция movt() записывает непосредственное значение в верхнее полуслово целевого регистра. Содержимое нижнего полуслова она не затрагивает.
 +
* movwt(Rd, imm32): Rd = imm32
 +
** Функция movwt() – это псевдо-команда: чтобы поместить 32-битное значение в Rd, ассемблер MicroPython генерирует movwt(), а затем вслед за movt().
 +
 +
==Чтение данных из регистра==
 +
 +
===Условные обозначения===
 +
 +
Под ''Rt'' и ''Rn'' подразумеваются ARM-регистры ''R0-R7'' (если не указано что-то другое). Под ''immN'' подразумевается непосредственное значение с разрешением в ''N бит'' – следовательно, ''imm5'' будет ограничено числами в диапазоне ''0-31''. Запись ''[Rn + immN]'' – это данные, находящиеся по адресу, который был получен сложением ''Rn'' и смещения ''immN''. Смещение измеряется в байтах. Эти инструкции влияют на флаги состояния.
 +
 +
===Чтение данных===
 +
 +
* ldr(Rt, [Rn, imm7]): Rt = [Rn + imm7] – чтение 32-битного слова
 +
* ldrb(Rt, [Rn, imm5]): Rt = [Rn + imm5] – чтение байта
 +
* ldrh(Rt, [Rn, imm6]): Rt = [Rn + imm6] – чтение 16-битного полуслова
 +
 +
При чтении байта или полуслова значение дополняется нулями, чтобы ''«дотянуть»'' его до 32 бит.
 +
 +
Указанные выше смещения измеряются в байтах. Таким образом, в случае ''ldr()'' и 7-битного смещения вы можете считывать 32-битные значения, выровненные по одному слову, с максимальным смещением в 31 слово. А в случае ldrh() и 6-битного смещения вы можете считывать 16-битные значения, выровненные по полуслову, с максимальным смещением в 31 полуслово.
 +
 +
==Запись данных в регистр==
 +
 +
===Условные обозначения===
 +
 +
Под ''Rt'' и ''Rn'' подразумеваются ARM-регистры ''R0-R7'' (если не указано что-то другое). Под ''immN'' подразумевается непосредственное значение с разрешением в ''N бит'' – следовательно, ''imm5'' будет ограничено числами в диапазоне ''0-31''. Под ''[Rn + imm5]'' подразумеваются данные, находящиеся по адресу, который был получен сложением ''Rn'' и смещения ''imm5''. Смещения измеряются в байтах. Эти инструкции не влияют на флаги состояния.
 +
 +
===Запись данных===
 +
 +
* str(Rt, [Rn, imm7]): [Rn + imm7] = Rt – запись 32-битного слова
 +
* strb(Rt, [Rn, imm5]): [Rn + imm5] = Rt – запись байта (b0-b7)
 +
* strh(Rt, [Rn, imm6]): [Rn + imm6] = Rt – запись 16-битного полуслова (b0-b15)
 +
 +
Указанные выше смещения измеряются в байтах. Таким образом, в случае ''str()'' и 7-битного смещения вы можете записывать 32-битные значения, выровненные по целому слову, с максимальным смещением в ''31 слово''. А в случае ''strh()'' и 6-битного смещения вы можете записывать ''16-битные значения'', выровненные по полуслову, с максимальным смещением в ''31 полуслово''.
 +
 +
==Логические команды и команды сдвига==
 +
 +
===Условные обозначения===
 +
 +
Под ''Rd'' и ''Rn'' подразумеваются ARM-регистры ''R0-R7'', за исключением специальных команд, где могут быть использованы регистры ''R0-R15''. Под ''Rn<a-b>'' подразумевается [[ARM-регистр]], чье содержимое должно находиться в диапазоне ''a <= число <= b''. В командах с двумя регистровыми аргументами их разрешается делать одинаковыми. Например, результатом функции ниже (в [[Python]]: ''R0 ^= R0''), выполняющей операцию ''«исключающее или»'', всегда будет ''«0»'' независимо от того, какое число хранилось в этом регистре до этой операции:
 +
 +
<syntaxhighlight lang="python" enclose="div">
 +
eor(r0, r0)
 +
</syntaxhighlight>
 +
 +
Эти команды влияют на флаги состояния (если не указано обратное).
 +
 +
===Логические команды===
 +
 +
* and_(Rd, Rn): Rd &= Rn
 +
* orr(Rd, Rn): Rd |= Rn
 +
* eor(Rd, Rn): Rd ^= Rn
 +
* mvn(Rd, Rn): Rd = Rn ^ 0xffffffff – то есть Rd является обратным двоичным кодом от Rn
 +
* bic(Rd, Rn): Rd &= ~Rn – сброс битов, т.е. побитовое «И» между Rd и инвертированным (при помощи побитового «НЕ») содержимым Rn
 +
 +
Обратите внимание, что здесь вместо ''and'' используется ''_and'', потому что and уже зарезервировано для ключевого слова в [[Python]].
 +
 +
===Команды сдвига===
 +
 +
* lsl(Rd, Rn<0-31>): Rd <<= Rn
 +
* lsr(Rd, Rn<1-32>): Rd = (Rd & 0xffffffff) >> Rn – логический сдвиг вправо
 +
* asr(Rd, Rn<1-32>): Rd >>= Rn – арифметический сдвиг вправо
 +
* ror(Rd, Rn<1-31>): Rd = rotate_right(Rd, Rn) – циклический сдвиг Rd вправо на количество бит в Rn
 +
 +
Циклический сдвиг, к примеру, на 3 бита работает следующим образом: если в ''Rd'' изначально хранятся биты ''b31 b30..b0'', то после циклического сдвига в нем будет содержаться ''b2 b1 b0 b31 b30..b3''.
 +
 +
===Специальные команды===
 +
 +
Эти инструкции не влияют на флаги состояния.
 +
* clz(Rd, Rn): Rd = count_leading_zeros(Rn)
 +
** Функция count_leading_zeros(Rn) возвращает количество нулей, стоящих до первой единицы в Rn.
 +
* rbit(Rd, Rn): Rd = bit_reverse(Rn)
 +
** Функция bit_reverse(Rn) возвращает инвертированное содержимое Rn. То есть, если в Rn содержится b31 b30..b0, в Rd в конечном счете окажется b0 b1 b2..b3.
 +
 +
Количество нулей в конце регистра можно определить, если сначала выполнить ''rbit()'', чтобы реверсировать порядок битов, а уже потом выполнить clz().
 +
 +
==Арифметические команды==
 +
 +
===Условные обозначения===
 +
 +
Под ''Rd'', ''Rm'' и ''Rn'' подразумеваются ARM-регистры ''R0-R7''. Под ''immN'' подразумевается непосредственное значение с разрешением в ''N бит'' – например, ''imm8'', ''imm3'' и т.д. Под перенос подразумевается флаг переноса, а под не(перенос) – заимствование.
 +
 +
Если у команды больше одного регистрового аргумента, их разрешается делать одинаковыми. Например, команда ниже добавит к R0 его же содержимое и сохранит результат опять же в ''R0'':
  
 
<syntaxhighlight lang="python" enclose="div">
 
<syntaxhighlight lang="python" enclose="div">
 +
add(r0, r0, r0)
 +
</syntaxhighlight>
 +
 +
Арифметические команды влияют на флаги состояния (если не указано другое).
 +
 +
===Сложение===
 +
 +
* add(Rdn, imm8): Rdn = Rdn + imm8
 +
* add(Rd, Rn, imm3): Rd = Rn + imm3
 +
* add(Rd, Rn, Rm): Rd = Rn +Rm
 +
* adc(Rd, Rn): Rd = Rd + Rn + перенос
 +
 +
===Вычитание===
 +
 +
* sub(Rdn, imm8): Rdn = Rdn - imm8
 +
* sub(Rd, Rn, imm3): Rd = Rn - imm3
 +
* sub(Rd, Rn, Rm): Rd = Rn - Rm
 +
* sbc(Rd, Rn): Rd = Rd - Rn - не(перенос)
 +
 +
===Отрицание===
 +
 +
* neg(Rd, Rn): Rd = -Rn
 +
 +
===Умножение и деление===
 +
 +
* mul(Rd, Rn): Rd = Rd * Rn
 +
 +
Генерирует 32-битный результат с потерей переполнения. Результатом может быть и знаковое, и беззнаковое значение – в зависимости от того, какие значения были в операндах.
 +
 +
* sdiv(Rd, Rn, Rm): Rd = Rn / Rm
 +
* udiv(Rd, Rn, Rm): Rd = Rn / Rm
 +
 +
Эти функции выполняют, соответственно, знаковое и беззнаковое деление. На флаги состояния эти команды не влияют.
 +
 +
==Команды сравнения==
 +
 +
Эти команды выполняют арифметические и логические операции с двумя аргументами – они не выдают никакого результата, но влияют на флаги состояния. Обычно они используются для тестирования данных без их изменения до выполнения условного перехода.
 +
 +
===Условные обозначения===
 +
 +
Под ''Rd'', ''Rm'' и ''Rn'' подразумеваются ARM-регистры ''R0-R7''. Под ''imm8'' подразумевается непосредственное значение с разрешением в ''8 бит''.
 +
 +
===Регистр состояния приложения (APSR)===
 +
 +
В этом регистре содержится ''4 бита'', которые используются командами условного перехода. Как правило, команда условного перехода – например, ''bge(МЕТКА)'' – проверяет несколько битов. То, за что отвечают флаги состояния, может зависеть от того, как арифметические команды видят операнды – как беззнаковые целые числа или целые числа со знаком. К примеру, команда ''bhi(МЕТКА)'' считает, что обрабатывает беззнаковые числа, а ''bgt(МЕТКА)'' – числа со знаком.
 +
 +
===Биты APSR-регистра===
 +
 +
* Z – ноль
 +
** В этом флаге будет задана единица, если результатом операции является ''«0»'' или сравниваемые операнды равны.
 +
* N – отрицательное значение
 +
** В этом флаге будет задана единица, если результат получился отрицательным.
 +
* C – перенос
 +
** Операция сложения задает в этом флаге единицу, если результат получится больше, чем может уместиться в 32-битном значении – например, при сложении ''«0x80000000»'' и ''«0x80000000»''. При вычитании этот механизм инвертируется по принципу вычислений с дополнительным кодом, и в результате при заимствовании в этом флаге ставится ноль. Соответственно, операция ''«0x10 - 0x01»'' выполняется как ''«0x10 + 0xffffffff»'', а в флаге переноса ставится единица.
 +
* V – переполнение
 +
** В этом флаге будет задана единица, если у результата (который будет считаться числом дополнительного кода) получился ''«неправильный»'' знак относительно операндов. К примеру, сложение ''«1»'' и ''«0x7fffffff»'' задаст в флаге V единицу, потому что результат (''«0x8000000»''), рассматриваемый как целое число дополнительного кода, получится отрицательным. Обратите внимание, что флаг переноса ''С'' (см. выше) в этом случае задан не будет.
 +
 +
===Команды сравнения===
 +
 +
Эти команды задают значения [[APSR-регистр]]а, то есть флаги ''N'' (отрицательное значение), ''Z'' (ноль), ''C'' (перенос) и ''V'' (переполнение).
 +
* cmp(Rn, imm8): Rn - imm8
 +
* cmp(Rn, Rm): Rn - Rm
 +
* cmn(Rn, Rm): Rn + Rm
 +
* tst(Rn, Rm): Rn & Rm
 +
 +
===Условные операторы===
 +
 +
Команды ''it'' и ''ite'' позволяют использовать условные операторы для ''1-4'' следующих дальше в коде команд без необходимости использовать метку.
 +
 +
* it(<условие>) If then
 +
** Эта команда запустит выполнение команды ниже, если <условие> в аргументе верно. Например:
 +
 +
<syntaxhighlight lang="python" enclose="div">
 +
cmp(r0, r1)
 +
it(eq)
 +
mov(r0, 100) # runs if r0 == r1
 +
# здесь будет совершаться выполнение
 +
</syntaxhighlight>
 +
 +
* ite(<условие>) If then else
 +
 +
Эта команда запустит выполнение первой команды, если ''<условие>'' в аргументе верно, а если неверно, будет выполнена вторая команда. Например:
 +
 +
<syntaxhighlight lang="python" enclose="div">
 +
cmp(r0, r1)
 +
ite(eq)
 +
mov(r0, 100) # runs if r0 == r1
 +
mov(r0, 200) # runs if r0 != r1
 +
# здесь будет совершаться выполнение
 +
</syntaxhighlight>
 +
 +
Функционал этих команд можно расширить вплоть до оперирования четырьмя командами. Это расширение делается по принципу ''it[x[y[z]]]'', где ''x'', ''y'' и ''z'' – это ''t (then)'' или ''e (else)''. В результате могут получиться команды ''itt'', ''itee'', ''itete'', ''ittte'', ''itttt'', ''iteee'' и т.д.
 +
 +
==Команды условных переходов==
 +
 +
Эти команды выполняют переход в заданное место, обычно задаваемое с помощью метки (более подробно о метках – ''label()'' – читайте в разделе [http://wikihandbk.com/wiki/MicroPython:%D0%9E%D1%81%D0%BD%D0%BE%D0%B2%D1%8B/%D0%AF%D0%B7%D1%8B%D0%BA_MicroPython_%D0%B8_%D0%B5%D0%B3%D0%BE_%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F/%D0%90%D1%81%D1%81%D0%B5%D0%BC%D0%B1%D0%BB%D0%B5%D1%80%D0%BD%D0%B0%D1%8F_%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B0_%D0%B4%D0%BB%D1%8F_%D0%B0%D1%80%D1%85%D0%B8%D1%82%D0%B5%D0%BA%D1%82%D1%83%D1%80_Thumb2#.D0.90.D1.81.D1.81.D0.B5.D0.BC.D0.B1.D0.BB.D0.B5.D1.80.D0.BD.D1.8B.D0.B5_.D0.B4.D0.B8.D1.80.D0.B5.D0.BA.D1.82.D0.B8.D0.B2.D1.8B «Ассемблерные директивы»]). Чтобы определить, нужно ли им делать переход, команды условных переходов, а также ''it'' и ''ite'' проверяют флаги ''N'' (отрицательное значение), ''Z'' (ноль), ''C'' (перенос) и ''V'' (переполнение) в регистре состояния приложения ([[APSR-регистр]]е).
 +
 +
На эти флаги влияют большинство ассемблерных команд, о которых рассказывается в этом руководстве (включая команды перемещения данных из раздела 1), но проверка этих флагов осуществляется при помощи команд сравнения, о которых рассказывалось в разделе ''1.10''.
 +
 +
Подробное описание того, за что отвечает каждый из этих флагов, читайте в разделе [http://wikihandbk.com/wiki/MicroPython:%D0%9E%D1%81%D0%BD%D0%BE%D0%B2%D1%8B/%D0%AF%D0%B7%D1%8B%D0%BA_MicroPython_%D0%B8_%D0%B5%D0%B3%D0%BE_%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F/%D0%90%D1%81%D1%81%D0%B5%D0%BC%D0%B1%D0%BB%D0%B5%D1%80%D0%BD%D0%B0%D1%8F_%D0%B2%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B0_%D0%B4%D0%BB%D1%8F_%D0%B0%D1%80%D1%85%D0%B8%D1%82%D0%B5%D0%BA%D1%82%D1%83%D1%80_Thumb2#.D0.9A.D0.BE.D0.BC.D0.B0.D0.BD.D0.B4.D1.8B_.D1.81.D1.80.D0.B0.D0.B2.D0.BD.D0.B5.D0.BD.D0.B8.D1.8F «6. Команды сравнения»].
 +
 +
===Условные обозначения===
 +
 +
Под Rm подразумеваются [[ARM-регистр]]ы ''R0-R15''. Под МЕТКА подразумевается метка, задаваемая с помощью ассемблерной директивы ''label()''. В <условие> можно задать одно из следующих условий:
 +
* eq – равно
 +
* ne – не равно
 +
* cs – в флаге переноса задана единица
 +
* cc – в флаге переноса задан ноль
 +
* mi – знак «минус» (отрицательное значение)
 +
* pl – знак «плюс» (положительное значение)
 +
* vs – в флаге переполнения задана единица
 +
* vc – в флаге переполнения задан ноль
 +
* hi – больше (беззнаковое сравнение)
 +
* ls – меньше или равно (беззнаковое сравнение)
 +
* ge – больше или равно (знаковое сравнение)
 +
* lt – меньше (знаковое сравнение)
 +
* gt – больше (знаковое сравнение)
 +
* le – меньше или равно (знаковое сравнение)
 +
 +
===Переходы по метке===
 +
 +
* b(МЕТКА) – безусловный переход
 +
* beq(МЕТКА) – переход, если равно
 +
* bne(МЕТКА) – переход, если не равно
 +
* bge(МЕТКА) – переход, если больше или равно
 +
* bgt(МЕТКА) – переход, если больше
 +
* blt(МЕТКА) – переход, если меньше (знаковое сравнение)
 +
* ble(МЕТКА) – переход, если меньше или равно (знаковое сравнение)
 +
* bcs(МЕТКА) – переход, если в флаге переноса стоит единица
 +
* bcc(МЕТКА) – переход, если в флаге переноса стоит ноль
 +
* bmi(МЕТКА) – переход, если у значения знак «минус»
 +
* bpl(МЕТКА) – переход, если у значения знак «плюс»
 +
* bvs(МЕТКА) – переход, если в флаге переполнения стоит единица
 +
* bvc(МЕТКА) – переход, если в флаге переполнения стоит ноль
 +
* bhi(МЕТКА) – переход, если выше (беззнаковое сравнение)
 +
* bls(МЕТКА) – переход, если меньше или равно (беззнаковое сравнение)
 +
 +
===Длинные переходы===
 +
 +
Чтобы задать место перехода, код, генерируемый командами условных переходов (см. выше), использует фиксированное битовое разрешение, зависящее от используемого [[ПК]]. Соответственно, в больших программах, где место назначения может быть далеко от исходной точки перехода, ассемблер сгенерирует ошибку типа «переход вне зоны досягаемости». Эту проблему можно решить с помощью ''«длинных»'' переходов вроде:
 +
* beq_w(МЕТКА) – длинный переход, если равно
 +
 +
При использовании длинного перехода команда занимает ''4 байта'' для кодировки команды (а у стандартных команд условных переходов используется 2 байта).
 +
 +
===Подпрограммы (функции)===
 +
 +
При входе в подпрограмму процессор сохраняет возвратный адрес в регистр ''R14'' (регистр связи или ''LR''). Возврат к команде после подпрограммы выполняется обновлением счетчика команд (''R15'' или ''PC'') из регистра связи. Этот процесс выполняется с помощью следующих инструкций:
 +
* bl(МЕТКА) – выполняет переход в МЕТКА и сохраняет возвратный адрес в регистр связи (R14).
 +
* bx(Rm) – выполняет переход на адрес, заданный в ''Rm''.
 +
 +
Как правило, чтобы вернуться из подпрограммы, ''bx()'' используется в виде ''bx(lr)''. Что касается вложенных подпрограмм, то перед выполнением внутренних подпрограмм сначала нужно сохранить (обычно в стек) возвратные адреса, находящиеся в наружных областях видимости.
 +
 +
==Команды записи и чтения из стека==
 +
 +
===Условные обозначения===
 +
 +
В аргументах команд ''push()'' и ''pop()'' можно задать некоторые или даже все регистры общего назначения ''R0-R12'' и вдобавок регистр связи (''LR'' или ''R14''). Как и всегда в [[Python]], порядок расстановки элементов не важен. Таким образом, в примере ниже ''pop()'' вернет регистрам ''R1, R7 и R8'' их значения даже несмотря на то, что порядок регистров в ''push()'' указан по-другому.
 +
 +
<syntaxhighlight lang="python" enclose="div">
 +
push({r1, r8, r7}) # сохраняем данные регистров в стек
 +
pop({r7, r1, r8})  # восстанавливаем данные регистров из стека
 +
</syntaxhighlight>
 +
 +
===Команды для работы со стеком===
 +
 +
* push({набор_регистров}) – сохраняем набор регистров в стек
 +
* pop({набор_регистров}) – восстанавливаем набор регистров из стека
 +
 +
==Прочие команды==
 +
 +
* nop() – не делает ничего.
 +
* wfi() – приостанавливает выполнение кода до возникновения прерывания, переводя устройство в режим пониженного энергопотребления.
 +
* cpsid(flags) – задает единицу в регистре маски приоритетов (отключает прерывания).
 +
* cpsie(flags) – задает ноль в регистре маски приоритетов (включает прерывания).
 +
* mrs(Rd, special_reg): Rd = special_reg – копирует данные из специального регистра в регистр общего назначения. Специальным регистром может быть [[IPSR]] (регистр состояния прерывания) или [[BASEPRI]] (регистр базового приоритета). Регистр [[IPSR]] позволяет определить номер обрабатываемого прерывания. Если в этом регистре содержится ''«0»'', то никакого прерывания в данный момент не обрабатывается.
 +
В настоящий момент функции ''cpsie()'' и ''cpsid()'' реализованы лишь частично – в них нужно указать аргумент ''flags'', но они на самом деле будут его игнорировать. Эти функции служат для включения и отключения прерываний.
 +
 +
==Команды для чисел с плавающей точкой==
 +
 +
Эти команды поддерживают использование [[ARM-сопроцессор]]а для чисел с плавающей точкой (таким оснащена, к примеру, [[Pyboard]]) – его также называют [[FPU]] (от англ. ''«floating point unit»'', что можно перевести как ''«модуль для операций с плавающей точкой»''). [[FPU-сопроцессор]] оснащен ''32 регистрами'' в диапазоне ''S0-S31'', каждый из которых может хранить одно число с плавающей точкой одинарной точности. Данные между [[FPU-регистр]]ами и основными [[ARM-регистр]]ами можно передавать при помощи команды ''vmov()''.
 +
 +
Помните, что в [[MicroPython]] не поддерживается передача чисел с плавающей точкой ассемблерным функциям, и также вряд ли какой-то адекватный результат получится, если поместить число с плавающей точкой в ''R0''. Эту проблему можно решить двумя способами. Во-первых, можно воспользоваться массивом, а во-вторых, можно передать и/или вернуть целое число, а потом в коде преобразовать его в число с плавающей точкой (или наоборот).
 +
 +
===Условные обозначения===
 +
 +
Под ''Sd'', ''Sm'' и ''Sn'' подразумеваются [[FPU-регистр]]ы, а под ''Rd'', ''Rm'' и ''Rn'' – главные [[ARM-регистр]]ы. В ''Rd'', ''Rm'' и ''Rn'' может быть любой основной [[ARM-регистр]], но регистры ''R13-R15'' плохо подходят для использования в этом контексте.
 +
 +
===Арифметические операции===
 +
 +
* vadd(Sd, Sn, Sm): Sd = Sn + Sm
 +
* vsub(Sd, Sn, Sm): Sd = Sn - Sm
 +
* vneg(Sd, Sm): Sd = -Sm
 +
* vmul(Sd, Sn, Sm): Sd = Sn * Sm
 +
* vdiv(Sd, Sn, Sm): Sd = Sn / Sm
 +
* vsqrt(Sd, Sm): Sd = sqrt(Sm)
 +
 +
В аргументах можно задавать одинаковые регистры. Например, ''vmul(S0, S0, S0)'' выполнит ''S0 = S0*S0.''
 +
 +
===Перемещение данных между ARM-регистрами и FPU-регистрами===
 +
 +
* vmov(Sd, Rm): Sd = Rm
 +
* vmov(Rd, Sm): Rd = Sm
 +
** [[FPU-сопроцессор]] оснащен регистром [[FPSCR]], который похож на основной [[ARM-регистр]] [[APSR]] – в нем хранятся флаги состояния и другие данные. Доступ к нему осуществляется с помощью команды ниже:
 +
* vmrs(APSR_nzcv, FPSCR)
 +
** Эта команда перемещает флаги ''N, Z, C и V'' для чисел с плавающей точкой в [[APSR-флаги]] ''N, Z, C и V''.
 +
 +
Ее выполняют, например, после сравнения чисел с плавающей точкой, чтобы ассемблерный код смог проинспектировать флаги состояния. Вот более распространенная форма этой команды:
 +
* vmrs(Rd, FPSCR): Rd = FPSCR
 +
 +
===Перемещение данных между FPU-регистром и памятью===
 +
 +
* vldr(Sd, [Rn, смещение]): Sd = [Rn + смещение]
 +
* vstr(Sd, [Rn, смещение]): [Rn + смещение] = Sd
 +
 +
Здесь под ''[Rn + смещение]'' подразумевается адрес памяти, полученный с помощью добавления ''Rn'' к смещению. Он указывается в байтах. Поскольку одно число с плавающей точкой занимает ''32-битное слово'', при доступе к массивам чисел с плавающей точкой смещение должно быть всегда кратно четырем байтам.
 +
 +
===Сравнение данных===
 +
 +
* vcmp(Sd, Sm)
 +
** Эта команда сравнивает значения в ''Sd'' и ''Sm'', а также задает [[FPU-флаги]] ''N, Z, C и V''. Обычно за ней также следует команда ''vmrs(APSR_nzcv, FPSCR)'' – чтобы проверить результат.
 +
 +
===Преобразование между целым числом или числом с плавающей точкой===
 +
 +
* vcvt_f32_s32(Sd, Sm): Sd = float(Sm)
 +
* vcvt_s32_f32(Sd, Sm): Sd = int(Sm)
 +
 +
==Ассемблерные директивы==
 +
 +
===Метки===
 +
 +
* label(INNER1)
 +
 +
Эта команда задает метку для последующего использования в команде условного перехода. Соответственно, если где-то дальше в коде будет стоять ''b(INNER1)'', то выполнение программы переместится в место, заданное директивой ''label()''.
 +
 +
===Команды для встраивания данных===
 +
 +
Ассемблерные директивы ниже помогают встроить данные в блок ассемблерного кода:
 +
 +
* data(размер, d0, d1 .. dn)
 +
 +
Директива ''data()'' создает в памяти массив ''n'' с данными. В первом аргументе размер задается, соответственно, размер (в байтах) последующих аргументов. Следовательно, во фрагменте кода ниже первый вызов ''data()'' поместит в смежные участки памяти три байта (со значениями ''«2»'', ''«3»'' и ''«4»''), а второй – два 4-байтных слова:
 +
 +
<syntaxhighlight lang="python" enclose="div">
 +
data(1, 2, 3, 4)
 +
data(4, 2, 100000)
 +
</syntaxhighlight>
 +
 +
Данные, чей размер превышает один байт, сохранятся в памяти в прямом порядке байтов.
 +
 +
* align(nBytes)
 +
 +
Эта команда выравнивает следующую инструкцию по размеру, заданному в аргументе ''nBytes''. Команды [[ARM Thumb-2]] должны быть ''выровнены под 2 байта''. Следовательно, после директивы ''data()'' рекомендуется всегда писать команду ''align(2)''. Благодаря этому код, который будет следовать дальше, будет запускаться безотносительно размера данных в массиве, созданном с помощью ''data()''.
  
 
=См.также=
 
=См.также=

Текущая версия на 15:43, 1 августа 2020

Перевод: Максим Кузьмин (Cubewriter) Контакты:</br>* Skype: cubewriter</br>* E-mail: cubewriter@gmail.com</br>* Максим Кузьмин на freelance.ru
Проверка/Оформление/Редактирование: Мякишев Е.А.


Содержание

Ассемблерная вставка для архитектур Thumb2[1]

Это руководство рассчитано на тех, кто уже более-менее знаком с ассемблерным программированием и ознакомился с этим руководством. Подробное описание команд читайте в справочнике по архитектуре ARM v7-M (см. ниже). В ассемблерной вставке MicroPython поддерживается использование некоторых команд из набора ARM Thumb-2 – именно о них и пойдет речь ниже. Эти команды переделаны в Python-функции, но их синтаксис максимально приближен к синтаксису из вышеупомянутого справочника по ARM v7-M.

Команды оперируют 32-битными знаковыми целочисленными данными, за исключением случаев, когда указано обратное. Большинство поддерживаемых команд работают только на регистрах R0-R7, но есть и команды, работающие на R8-R15 (в этом случае об этом будет сказано в ее описании). Перед тем, как функция вернет результат, регистрам R8-R12 должно быть возвращено их первоначальное значение. Регистры R13-R15 – это, соответственно, регистр связи, указатель стека и счетчик программ.

Условные обозначения и правила оформления кода

Где это возможно, поведение каждой команды описано на Python. Например:

add(Rd, Rn, Rm): Rd = Rn + Rm

Это позволяет продемонстрировать работу этих функций на примере Python-кода. В некоторых случаях это невозможно, потому что в Python не поддерживаются концепты вроде косвенной адресации. Псевдокод, используемый в таких ситуациях, описан на соответствующей странице.

Типы команд

В разделах ниже содержится описание команд ARM Thumb-2, поддерживаемых в MicroPython:

  1. Команды перемещения данных
  2. Команды чтения данных из регистра
  3. Команды записи данных в регистр
  4. Логические команды и команды сдвига
  5. Арифметические команды
  6. Команды сравнения
  7. Команды условных переходов
  8. Команды записи и чтения из стека
  9. Прочие команды
  10. Команды для чисел с плавающей точкой
  11. Ассемблерные директивы

Примеры использования

В этом разделе можно найти примеры и советы по использованию ассемблерного кода.

  1. Советы по использованию ассемблерных вставок

Справочные материалы

Команды перемещения данных

Условные обозначения

Под Rd и Rn подразумеваются ARM-регистры R0-R15. Под immN подразумевается непосредственное значение с разрешением в N бит. Эти инструкции влияют на флаги состояния.

Перемещение данных в регистр

Если используется непосредственное значение, оно будет дополнено нулями, чтобы «дотянуть» его до 32 бит. Таким образом, mov(R0, 0xff) запишет в R0 значение «255».

  • mov(Rd, imm8): Rd = imm8
  • mov(Rd, Rn): Rd = Rn
  • movw(Rd, imm16): Rd = imm16
  • movt(Rd, imm16): Rd = (Rd & 0xffff) | (imm16 << 16)
    • Функция movt() записывает непосредственное значение в верхнее полуслово целевого регистра. Содержимое нижнего полуслова она не затрагивает.
  • movwt(Rd, imm32): Rd = imm32
    • Функция movwt() – это псевдо-команда: чтобы поместить 32-битное значение в Rd, ассемблер MicroPython генерирует movwt(), а затем вслед за movt().

Чтение данных из регистра

Условные обозначения

Под Rt и Rn подразумеваются ARM-регистры R0-R7 (если не указано что-то другое). Под immN подразумевается непосредственное значение с разрешением в N бит – следовательно, imm5 будет ограничено числами в диапазоне 0-31. Запись [Rn + immN] – это данные, находящиеся по адресу, который был получен сложением Rn и смещения immN. Смещение измеряется в байтах. Эти инструкции влияют на флаги состояния.

Чтение данных

  • ldr(Rt, [Rn, imm7]): Rt = [Rn + imm7] – чтение 32-битного слова
  • ldrb(Rt, [Rn, imm5]): Rt = [Rn + imm5] – чтение байта
  • ldrh(Rt, [Rn, imm6]): Rt = [Rn + imm6] – чтение 16-битного полуслова

При чтении байта или полуслова значение дополняется нулями, чтобы «дотянуть» его до 32 бит.

Указанные выше смещения измеряются в байтах. Таким образом, в случае ldr() и 7-битного смещения вы можете считывать 32-битные значения, выровненные по одному слову, с максимальным смещением в 31 слово. А в случае ldrh() и 6-битного смещения вы можете считывать 16-битные значения, выровненные по полуслову, с максимальным смещением в 31 полуслово.

Запись данных в регистр

Условные обозначения

Под Rt и Rn подразумеваются ARM-регистры R0-R7 (если не указано что-то другое). Под immN подразумевается непосредственное значение с разрешением в N бит – следовательно, imm5 будет ограничено числами в диапазоне 0-31. Под [Rn + imm5] подразумеваются данные, находящиеся по адресу, который был получен сложением Rn и смещения imm5. Смещения измеряются в байтах. Эти инструкции не влияют на флаги состояния.

Запись данных

  • str(Rt, [Rn, imm7]): [Rn + imm7] = Rt – запись 32-битного слова
  • strb(Rt, [Rn, imm5]): [Rn + imm5] = Rt – запись байта (b0-b7)
  • strh(Rt, [Rn, imm6]): [Rn + imm6] = Rt – запись 16-битного полуслова (b0-b15)

Указанные выше смещения измеряются в байтах. Таким образом, в случае str() и 7-битного смещения вы можете записывать 32-битные значения, выровненные по целому слову, с максимальным смещением в 31 слово. А в случае strh() и 6-битного смещения вы можете записывать 16-битные значения, выровненные по полуслову, с максимальным смещением в 31 полуслово.

Логические команды и команды сдвига

Условные обозначения

Под Rd и Rn подразумеваются ARM-регистры R0-R7, за исключением специальных команд, где могут быть использованы регистры R0-R15. Под Rn<a-b> подразумевается ARM-регистр, чье содержимое должно находиться в диапазоне a <= число <= b. В командах с двумя регистровыми аргументами их разрешается делать одинаковыми. Например, результатом функции ниже (в Python: R0 ^= R0), выполняющей операцию «исключающее или», всегда будет «0» независимо от того, какое число хранилось в этом регистре до этой операции:

eor(r0, r0)

Эти команды влияют на флаги состояния (если не указано обратное).

Логические команды

  • and_(Rd, Rn): Rd &= Rn
  • orr(Rd, Rn): Rd |= Rn
  • eor(Rd, Rn): Rd ^= Rn
  • mvn(Rd, Rn): Rd = Rn ^ 0xffffffff – то есть Rd является обратным двоичным кодом от Rn
  • bic(Rd, Rn): Rd &= ~Rn – сброс битов, т.е. побитовое «И» между Rd и инвертированным (при помощи побитового «НЕ») содержимым Rn

Обратите внимание, что здесь вместо and используется _and, потому что and уже зарезервировано для ключевого слова в Python.

Команды сдвига

  • lsl(Rd, Rn<0-31>): Rd <<= Rn
  • lsr(Rd, Rn<1-32>): Rd = (Rd & 0xffffffff) >> Rn – логический сдвиг вправо
  • asr(Rd, Rn<1-32>): Rd >>= Rn – арифметический сдвиг вправо
  • ror(Rd, Rn<1-31>): Rd = rotate_right(Rd, Rn) – циклический сдвиг Rd вправо на количество бит в Rn

Циклический сдвиг, к примеру, на 3 бита работает следующим образом: если в Rd изначально хранятся биты b31 b30..b0, то после циклического сдвига в нем будет содержаться b2 b1 b0 b31 b30..b3.

Специальные команды

Эти инструкции не влияют на флаги состояния.

  • clz(Rd, Rn): Rd = count_leading_zeros(Rn)
    • Функция count_leading_zeros(Rn) возвращает количество нулей, стоящих до первой единицы в Rn.
  • rbit(Rd, Rn): Rd = bit_reverse(Rn)
    • Функция bit_reverse(Rn) возвращает инвертированное содержимое Rn. То есть, если в Rn содержится b31 b30..b0, в Rd в конечном счете окажется b0 b1 b2..b3.

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

Арифметические команды

Условные обозначения

Под Rd, Rm и Rn подразумеваются ARM-регистры R0-R7. Под immN подразумевается непосредственное значение с разрешением в N бит – например, imm8, imm3 и т.д. Под перенос подразумевается флаг переноса, а под не(перенос) – заимствование.

Если у команды больше одного регистрового аргумента, их разрешается делать одинаковыми. Например, команда ниже добавит к R0 его же содержимое и сохранит результат опять же в R0:

add(r0, r0, r0)

Арифметические команды влияют на флаги состояния (если не указано другое).

Сложение

  • add(Rdn, imm8): Rdn = Rdn + imm8
  • add(Rd, Rn, imm3): Rd = Rn + imm3
  • add(Rd, Rn, Rm): Rd = Rn +Rm
  • adc(Rd, Rn): Rd = Rd + Rn + перенос

Вычитание

  • sub(Rdn, imm8): Rdn = Rdn - imm8
  • sub(Rd, Rn, imm3): Rd = Rn - imm3
  • sub(Rd, Rn, Rm): Rd = Rn - Rm
  • sbc(Rd, Rn): Rd = Rd - Rn - не(перенос)

Отрицание

  • neg(Rd, Rn): Rd = -Rn

Умножение и деление

  • mul(Rd, Rn): Rd = Rd * Rn

Генерирует 32-битный результат с потерей переполнения. Результатом может быть и знаковое, и беззнаковое значение – в зависимости от того, какие значения были в операндах.

  • sdiv(Rd, Rn, Rm): Rd = Rn / Rm
  • udiv(Rd, Rn, Rm): Rd = Rn / Rm

Эти функции выполняют, соответственно, знаковое и беззнаковое деление. На флаги состояния эти команды не влияют.

Команды сравнения

Эти команды выполняют арифметические и логические операции с двумя аргументами – они не выдают никакого результата, но влияют на флаги состояния. Обычно они используются для тестирования данных без их изменения до выполнения условного перехода.

Условные обозначения

Под Rd, Rm и Rn подразумеваются ARM-регистры R0-R7. Под imm8 подразумевается непосредственное значение с разрешением в 8 бит.

Регистр состояния приложения (APSR)

В этом регистре содержится 4 бита, которые используются командами условного перехода. Как правило, команда условного перехода – например, bge(МЕТКА) – проверяет несколько битов. То, за что отвечают флаги состояния, может зависеть от того, как арифметические команды видят операнды – как беззнаковые целые числа или целые числа со знаком. К примеру, команда bhi(МЕТКА) считает, что обрабатывает беззнаковые числа, а bgt(МЕТКА) – числа со знаком.

Биты APSR-регистра

  • Z – ноль
    • В этом флаге будет задана единица, если результатом операции является «0» или сравниваемые операнды равны.
  • N – отрицательное значение
    • В этом флаге будет задана единица, если результат получился отрицательным.
  • C – перенос
    • Операция сложения задает в этом флаге единицу, если результат получится больше, чем может уместиться в 32-битном значении – например, при сложении «0x80000000» и «0x80000000». При вычитании этот механизм инвертируется по принципу вычислений с дополнительным кодом, и в результате при заимствовании в этом флаге ставится ноль. Соответственно, операция «0x10 - 0x01» выполняется как «0x10 + 0xffffffff», а в флаге переноса ставится единица.
  • V – переполнение
    • В этом флаге будет задана единица, если у результата (который будет считаться числом дополнительного кода) получился «неправильный» знак относительно операндов. К примеру, сложение «1» и «0x7fffffff» задаст в флаге V единицу, потому что результат («0x8000000»), рассматриваемый как целое число дополнительного кода, получится отрицательным. Обратите внимание, что флаг переноса С (см. выше) в этом случае задан не будет.

Команды сравнения

Эти команды задают значения APSR-регистра, то есть флаги N (отрицательное значение), Z (ноль), C (перенос) и V (переполнение).

  • cmp(Rn, imm8): Rn - imm8
  • cmp(Rn, Rm): Rn - Rm
  • cmn(Rn, Rm): Rn + Rm
  • tst(Rn, Rm): Rn & Rm

Условные операторы

Команды it и ite позволяют использовать условные операторы для 1-4 следующих дальше в коде команд без необходимости использовать метку.

  • it(<условие>) If then
    • Эта команда запустит выполнение команды ниже, если <условие> в аргументе верно. Например:
cmp(r0, r1)
it(eq)
mov(r0, 100) # runs if r0 == r1
# здесь будет совершаться выполнение
  • ite(<условие>) If then else

Эта команда запустит выполнение первой команды, если <условие> в аргументе верно, а если неверно, будет выполнена вторая команда. Например:

cmp(r0, r1)
ite(eq)
mov(r0, 100) # runs if r0 == r1
mov(r0, 200) # runs if r0 != r1
# здесь будет совершаться выполнение

Функционал этих команд можно расширить вплоть до оперирования четырьмя командами. Это расширение делается по принципу it[x[y[z]]], где x, y и z – это t (then) или e (else). В результате могут получиться команды itt, itee, itete, ittte, itttt, iteee и т.д.

Команды условных переходов

Эти команды выполняют переход в заданное место, обычно задаваемое с помощью метки (более подробно о метках – label() – читайте в разделе «Ассемблерные директивы»). Чтобы определить, нужно ли им делать переход, команды условных переходов, а также it и ite проверяют флаги N (отрицательное значение), Z (ноль), C (перенос) и V (переполнение) в регистре состояния приложения (APSR-регистре).

На эти флаги влияют большинство ассемблерных команд, о которых рассказывается в этом руководстве (включая команды перемещения данных из раздела 1), но проверка этих флагов осуществляется при помощи команд сравнения, о которых рассказывалось в разделе 1.10.

Подробное описание того, за что отвечает каждый из этих флагов, читайте в разделе «6. Команды сравнения».

Условные обозначения

Под Rm подразумеваются ARM-регистры R0-R15. Под МЕТКА подразумевается метка, задаваемая с помощью ассемблерной директивы label(). В <условие> можно задать одно из следующих условий:

  • eq – равно
  • ne – не равно
  • cs – в флаге переноса задана единица
  • cc – в флаге переноса задан ноль
  • mi – знак «минус» (отрицательное значение)
  • pl – знак «плюс» (положительное значение)
  • vs – в флаге переполнения задана единица
  • vc – в флаге переполнения задан ноль
  • hi – больше (беззнаковое сравнение)
  • ls – меньше или равно (беззнаковое сравнение)
  • ge – больше или равно (знаковое сравнение)
  • lt – меньше (знаковое сравнение)
  • gt – больше (знаковое сравнение)
  • le – меньше или равно (знаковое сравнение)

Переходы по метке

  • b(МЕТКА) – безусловный переход
  • beq(МЕТКА) – переход, если равно
  • bne(МЕТКА) – переход, если не равно
  • bge(МЕТКА) – переход, если больше или равно
  • bgt(МЕТКА) – переход, если больше
  • blt(МЕТКА) – переход, если меньше (знаковое сравнение)
  • ble(МЕТКА) – переход, если меньше или равно (знаковое сравнение)
  • bcs(МЕТКА) – переход, если в флаге переноса стоит единица
  • bcc(МЕТКА) – переход, если в флаге переноса стоит ноль
  • bmi(МЕТКА) – переход, если у значения знак «минус»
  • bpl(МЕТКА) – переход, если у значения знак «плюс»
  • bvs(МЕТКА) – переход, если в флаге переполнения стоит единица
  • bvc(МЕТКА) – переход, если в флаге переполнения стоит ноль
  • bhi(МЕТКА) – переход, если выше (беззнаковое сравнение)
  • bls(МЕТКА) – переход, если меньше или равно (беззнаковое сравнение)

Длинные переходы

Чтобы задать место перехода, код, генерируемый командами условных переходов (см. выше), использует фиксированное битовое разрешение, зависящее от используемого ПК. Соответственно, в больших программах, где место назначения может быть далеко от исходной точки перехода, ассемблер сгенерирует ошибку типа «переход вне зоны досягаемости». Эту проблему можно решить с помощью «длинных» переходов вроде:

  • beq_w(МЕТКА) – длинный переход, если равно

При использовании длинного перехода команда занимает 4 байта для кодировки команды (а у стандартных команд условных переходов используется 2 байта).

Подпрограммы (функции)

При входе в подпрограмму процессор сохраняет возвратный адрес в регистр R14 (регистр связи или LR). Возврат к команде после подпрограммы выполняется обновлением счетчика команд (R15 или PC) из регистра связи. Этот процесс выполняется с помощью следующих инструкций:

  • bl(МЕТКА) – выполняет переход в МЕТКА и сохраняет возвратный адрес в регистр связи (R14).
  • bx(Rm) – выполняет переход на адрес, заданный в Rm.

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

Команды записи и чтения из стека

Условные обозначения

В аргументах команд push() и pop() можно задать некоторые или даже все регистры общего назначения R0-R12 и вдобавок регистр связи (LR или R14). Как и всегда в Python, порядок расстановки элементов не важен. Таким образом, в примере ниже pop() вернет регистрам R1, R7 и R8 их значения даже несмотря на то, что порядок регистров в push() указан по-другому.

push({r1, r8, r7}) # сохраняем данные регистров в стек
pop({r7, r1, r8})  # восстанавливаем данные регистров из стека

Команды для работы со стеком

  • push({набор_регистров}) – сохраняем набор регистров в стек
  • pop({набор_регистров}) – восстанавливаем набор регистров из стека

Прочие команды

  • nop() – не делает ничего.
  • wfi() – приостанавливает выполнение кода до возникновения прерывания, переводя устройство в режим пониженного энергопотребления.
  • cpsid(flags) – задает единицу в регистре маски приоритетов (отключает прерывания).
  • cpsie(flags) – задает ноль в регистре маски приоритетов (включает прерывания).
  • mrs(Rd, special_reg): Rd = special_reg – копирует данные из специального регистра в регистр общего назначения. Специальным регистром может быть IPSR (регистр состояния прерывания) или BASEPRI (регистр базового приоритета). Регистр IPSR позволяет определить номер обрабатываемого прерывания. Если в этом регистре содержится «0», то никакого прерывания в данный момент не обрабатывается.

В настоящий момент функции cpsie() и cpsid() реализованы лишь частично – в них нужно указать аргумент flags, но они на самом деле будут его игнорировать. Эти функции служат для включения и отключения прерываний.

Команды для чисел с плавающей точкой

Эти команды поддерживают использование ARM-сопроцессора для чисел с плавающей точкой (таким оснащена, к примеру, Pyboard) – его также называют FPU (от англ. «floating point unit», что можно перевести как «модуль для операций с плавающей точкой»). FPU-сопроцессор оснащен 32 регистрами в диапазоне S0-S31, каждый из которых может хранить одно число с плавающей точкой одинарной точности. Данные между FPU-регистрами и основными ARM-регистрами можно передавать при помощи команды vmov().

Помните, что в MicroPython не поддерживается передача чисел с плавающей точкой ассемблерным функциям, и также вряд ли какой-то адекватный результат получится, если поместить число с плавающей точкой в R0. Эту проблему можно решить двумя способами. Во-первых, можно воспользоваться массивом, а во-вторых, можно передать и/или вернуть целое число, а потом в коде преобразовать его в число с плавающей точкой (или наоборот).

Условные обозначения

Под Sd, Sm и Sn подразумеваются FPU-регистры, а под Rd, Rm и Rn – главные ARM-регистры. В Rd, Rm и Rn может быть любой основной ARM-регистр, но регистры R13-R15 плохо подходят для использования в этом контексте.

Арифметические операции

  • vadd(Sd, Sn, Sm): Sd = Sn + Sm
  • vsub(Sd, Sn, Sm): Sd = Sn - Sm
  • vneg(Sd, Sm): Sd = -Sm
  • vmul(Sd, Sn, Sm): Sd = Sn * Sm
  • vdiv(Sd, Sn, Sm): Sd = Sn / Sm
  • vsqrt(Sd, Sm): Sd = sqrt(Sm)

В аргументах можно задавать одинаковые регистры. Например, vmul(S0, S0, S0) выполнит S0 = S0*S0.

Перемещение данных между ARM-регистрами и FPU-регистрами

  • vmov(Sd, Rm): Sd = Rm
  • vmov(Rd, Sm): Rd = Sm
    • FPU-сопроцессор оснащен регистром FPSCR, который похож на основной ARM-регистр APSR – в нем хранятся флаги состояния и другие данные. Доступ к нему осуществляется с помощью команды ниже:
  • vmrs(APSR_nzcv, FPSCR)
    • Эта команда перемещает флаги N, Z, C и V для чисел с плавающей точкой в APSR-флаги N, Z, C и V.

Ее выполняют, например, после сравнения чисел с плавающей точкой, чтобы ассемблерный код смог проинспектировать флаги состояния. Вот более распространенная форма этой команды:

  • vmrs(Rd, FPSCR): Rd = FPSCR

Перемещение данных между FPU-регистром и памятью

  • vldr(Sd, [Rn, смещение]): Sd = [Rn + смещение]
  • vstr(Sd, [Rn, смещение]): [Rn + смещение] = Sd

Здесь под [Rn + смещение] подразумевается адрес памяти, полученный с помощью добавления Rn к смещению. Он указывается в байтах. Поскольку одно число с плавающей точкой занимает 32-битное слово, при доступе к массивам чисел с плавающей точкой смещение должно быть всегда кратно четырем байтам.

Сравнение данных

  • vcmp(Sd, Sm)
    • Эта команда сравнивает значения в Sd и Sm, а также задает FPU-флаги N, Z, C и V. Обычно за ней также следует команда vmrs(APSR_nzcv, FPSCR) – чтобы проверить результат.

Преобразование между целым числом или числом с плавающей точкой

  • vcvt_f32_s32(Sd, Sm): Sd = float(Sm)
  • vcvt_s32_f32(Sd, Sm): Sd = int(Sm)

Ассемблерные директивы

Метки

  • label(INNER1)

Эта команда задает метку для последующего использования в команде условного перехода. Соответственно, если где-то дальше в коде будет стоять b(INNER1), то выполнение программы переместится в место, заданное директивой label().

Команды для встраивания данных

Ассемблерные директивы ниже помогают встроить данные в блок ассемблерного кода:

  • data(размер, d0, d1 .. dn)

Директива data() создает в памяти массив n с данными. В первом аргументе размер задается, соответственно, размер (в байтах) последующих аргументов. Следовательно, во фрагменте кода ниже первый вызов data() поместит в смежные участки памяти три байта (со значениями «2», «3» и «4»), а второй – два 4-байтных слова:

data(1, 2, 3, 4)
data(4, 2, 100000)

Данные, чей размер превышает один байт, сохранятся в памяти в прямом порядке байтов.

  • align(nBytes)

Эта команда выравнивает следующую инструкцию по размеру, заданному в аргументе nBytes. Команды ARM Thumb-2 должны быть выровнены под 2 байта. Следовательно, после директивы data() рекомендуется всегда писать команду align(2). Благодаря этому код, который будет следовать дальше, будет запускаться безотносительно размера данных в массиве, созданном с помощью data().

См.также

Внешние ссылки