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

Материал из Онлайн справочника
Перейти к навигацииПерейти к поиску
Нет описания правки
Строка 52: Строка 52:
===Условные обозначения===
===Условные обозначения===


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


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


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


==Чтение данных из регистра==
==Чтение данных из регистра==
Строка 69: Строка 69:
===Условные обозначения===
===Условные обозначения===


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


===Чтение данных===
===Чтение данных===
Строка 77: Строка 77:
* ldrh(Rt, [Rn, imm6]): Rt = [Rn + imm6] – чтение 16-битного полуслова
* ldrh(Rt, [Rn, imm6]): Rt = [Rn + imm6] – чтение 16-битного полуслова


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


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


==Запись данных в регистр==
==Запись данных в регистр==
Строка 85: Строка 85:
===Условные обозначения===
===Условные обозначения===


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


===Запись данных===
===Запись данных===
Строка 93: Строка 93:
* strh(Rt, [Rn, imm6]): [Rn + imm6] = Rt – запись 16-битного полуслова (b0-b15)
* strh(Rt, [Rn, imm6]): [Rn + imm6] = Rt – запись 16-битного полуслова (b0-b15)


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


==Логические команды и команды сдвига==
==Логические команды и команды сдвига==
Строка 99: Строка 99:
===Условные обозначения===
===Условные обозначения===


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


Эти команды влияют на флаги состояния (если не указано обратное).
Эти команды влияют на флаги состояния (если не указано обратное).
Строка 113: Строка 115:
* bic(Rd, Rn): Rd &= ~Rn – сброс битов, т.е. побитовое «И» между Rd и инвертированным (при помощи побитового «НЕ») содержимым Rn
* bic(Rd, Rn): Rd &= ~Rn – сброс битов, т.е. побитовое «И» между Rd и инвертированным (при помощи побитового «НЕ») содержимым Rn


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


===Команды сдвига===
===Команды сдвига===
Строка 122: Строка 124:
* ror(Rd, Rn<1-31>): Rd = rotate_right(Rd, Rn) – циклический сдвиг 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.
Циклический сдвиг, к примеру, на 3 бита работает следующим образом: если в ''Rd'' изначально хранятся биты ''b31 b30..b0'', то после циклического сдвига в нем будет содержаться ''b2 b1 b0 b31 b30..b3''.


===Специальные команды===
===Специальные команды===
Строка 128: Строка 130:
Эти инструкции не влияют на флаги состояния.
Эти инструкции не влияют на флаги состояния.
* clz(Rd, Rn): Rd = count_leading_zeros(Rn)
* clz(Rd, Rn): Rd = count_leading_zeros(Rn)
Функция count_leading_zeros(Rn) возвращает количество нулей, стоящих до первой единицы в Rn.
** Функция count_leading_zeros(Rn) возвращает количество нулей, стоящих до первой единицы в Rn.
* rbit(Rd, Rn): Rd = bit_reverse(Rn)
* rbit(Rd, Rn): Rd = bit_reverse(Rn)
Функция bit_reverse(Rn) возвращает инвертированное содержимое Rn. То есть, если в Rn содержится b31 b30..b0, в Rd в конечном счете окажется b0 b1 b2..b3.
** Функция bit_reverse(Rn) возвращает инвертированное содержимое Rn. То есть, если в Rn содержится b31 b30..b0, в Rd в конечном счете окажется b0 b1 b2..b3.
Количество нулей в конце регистра можно определить, если сначала выполнить rbit(), чтобы реверсировать порядок битов, а уже потом выполнить clz().
 
Количество нулей в конце регистра можно определить, если сначала выполнить ''rbit()'', чтобы реверсировать порядок битов, а уже потом выполнить clz().


==Арифметические команды==
==Арифметические команды==
Строка 137: Строка 140:
===Условные обозначения===
===Условные обозначения===


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


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


<syntaxhighlight lang="python" enclose="div">
add(r0, r0, r0)
add(r0, r0, r0)
</syntaxhighlight>


Арифметические команды влияют на флаги состояния (если не указано другое).
Арифметические команды влияют на флаги состояния (если не указано другое).
Строка 180: Строка 185:
===Условные обозначения===
===Условные обозначения===


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


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


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


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


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


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


Эти команды задают значения APSR-регистра, то есть флаги N (отрицательное значение), Z (ноль), C (перенос) и V (переполнение).
Эти команды задают значения [[APSR-регистр]]а, то есть флаги ''N'' (отрицательное значение), ''Z'' (ноль), ''C'' (перенос) и ''V'' (переполнение).
* cmp(Rn, imm8): Rn - imm8
* cmp(Rn, imm8): Rn - imm8
* cmp(Rn, Rm): Rn - Rm
* cmp(Rn, Rm): Rn - Rm
Строка 207: Строка 212:
===Условные операторы===
===Условные операторы===


Команды it и ite позволяют использовать условные операторы для 1-4 следующих дальше в коде команд без необходимости использовать метку.
Команды ''it'' и ''ite'' позволяют использовать условные операторы для ''1-4'' следующих дальше в коде команд без необходимости использовать метку.
 
* it(<условие>) If then
* it(<условие>) If then
Эта команда запустит выполнение команды ниже, если <условие> в аргументе верно. Например:
** Эта команда запустит выполнение команды ниже, если <условие> в аргументе верно. Например:


<syntaxhighlight lang="python" enclose="div">
cmp(r0, r1)
cmp(r0, r1)
it(eq)
it(eq)
mov(r0, 100) # runs if r0 == r1
mov(r0, 100) # runs if r0 == r1
# здесь будет совершаться выполнение
# здесь будет совершаться выполнение
</syntaxhighlight>


* ite(<условие>) If then else
* ite(<условие>) If then else


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


<syntaxhighlight lang="python" enclose="div">
cmp(r0, r1)
cmp(r0, r1)
ite(eq)
ite(eq)
Строка 225: Строка 234:
mov(r0, 200) # 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 и т.д.  
Функционал этих команд можно расширить вплоть до оперирования четырьмя командами. Это расширение делается по принципу ''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-регистре).
Эти команды выполняют переход в заданное место, обычно задаваемое с помощью метки (более подробно о метках – ''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), но проверка этих флагов осуществляется при помощи команд сравнения, о которых рассказывалось в разделе 6.
На эти флаги влияют большинство ассемблерных команд, о которых рассказывается в этом руководстве (включая команды перемещения данных из раздела 1), но проверка этих флагов осуществляется при помощи команд сравнения, о которых рассказывалось в разделе ''1.10''.


Подробное описание того, за что отвечает каждый из этих флагов, читайте в разделе «6. Команды сравнения».
Подробное описание того, за что отвечает каждый из этих флагов, читайте в разделе [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(). В <условие> можно задать одно из следующих условий:
Под Rm подразумеваются [[ARM-регистр]]ы ''R0-R15''. Под МЕТКА подразумевается метка, задаваемая с помощью ассемблерной директивы ''label()''. В <условие> можно задать одно из следующих условий:
* eq – равно
* eq – равно
* ne – не равно
* ne – не равно
Строка 274: Строка 284:
===Длинные переходы===
===Длинные переходы===


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


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


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


При входе в подпрограмму процессор сохраняет возвратный адрес в регистр R14 (регистр связи или LR). Возврат к команде после подпрограммы выполняется обновлением счетчика команд (R15 или PC) из регистра связи. Этот процесс выполняется с помощью следующих инструкций:
При входе в подпрограмму процессор сохраняет возвратный адрес в регистр ''R14'' (регистр связи или ''LR''). Возврат к команде после подпрограммы выполняется обновлением счетчика команд (''R15'' или ''PC'') из регистра связи. Этот процесс выполняется с помощью следующих инструкций:
* bl(МЕТКА) – выполняет переход в МЕТКА и сохраняет возвратный адрес в регистр связи (R14).
* bl(МЕТКА) – выполняет переход в МЕТКА и сохраняет возвратный адрес в регистр связи (R14).
* bx(Rm) – выполняет переход на адрес, заданный в Rm.
* bx(Rm) – выполняет переход на адрес, заданный в ''Rm''.


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


==Команды записи и чтения из стека==
==Команды записи и чтения из стека==
Строка 291: Строка 301:
===Условные обозначения===
===Условные обозначения===


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


<syntaxhighlight lang="python" enclose="div">
push({r1, r8, r7}) # сохраняем данные регистров в стек
push({r1, r8, r7}) # сохраняем данные регистров в стек
pop({r7, r1, r8})  # восстанавливаем данные регистров из стека
pop({r7, r1, r8})  # восстанавливаем данные регистров из стека
</syntaxhighlight>


===Команды для работы со стеком===
===Команды для работы со стеком===
Строка 307: Строка 319:
* cpsid(flags) – задает единицу в регистре маски приоритетов (отключает прерывания).
* cpsid(flags) – задает единицу в регистре маски приоритетов (отключает прерывания).
* cpsie(flags) – задает ноль в регистре маски приоритетов (включает прерывания).
* cpsie(flags) – задает ноль в регистре маски приоритетов (включает прерывания).
* mrs(Rd, special_reg): Rd = special_reg – копирует данные из специального регистра в регистр общего назначения. Специальным регистром может быть IPSR (регистр состояния прерывания) или BASEPRI (регистр базового приоритета). Регистр  
* mrs(Rd, special_reg): Rd = special_reg – копирует данные из специального регистра в регистр общего назначения. Специальным регистром может быть [[IPSR]] (регистр состояния прерывания) или [[BASEPRI]] (регистр базового приоритета). Регистр [[IPSR]] позволяет определить номер обрабатываемого прерывания. Если в этом регистре содержится ''«0»'', то никакого прерывания в данный момент не обрабатывается.
IPSR позволяет определить номер обрабатываемого прерывания. Если в этом регистре содержится «0», то никакого прерывания в данный момент не обрабатывается.
В настоящий момент функции ''cpsie()'' и ''cpsid()'' реализованы лишь частично – в них нужно указать аргумент ''flags'', но они на самом деле будут его игнорировать. Эти функции служат для включения и отключения прерываний.
В настоящий момент функции cpsie() и cpsid() реализованы лишь частично – в них нужно указать аргумент flags, но они на самом деле будут его игнорировать. Эти функции служат для включения и отключения прерываний.


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


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


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


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


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


===Арифметические операции===
===Арифметические операции===
Строка 330: Строка 341:
* vsqrt(Sd, Sm): Sd = sqrt(Sm)
* vsqrt(Sd, Sm): Sd = sqrt(Sm)


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


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


Эта команда перемещает флаги N, Z, C и V для чисел с плавающей точкой в APSR-флаги N, Z, C и V.
Ее выполняют, например, после сравнения чисел с плавающей точкой, чтобы ассемблерный код смог проинспектировать флаги состояния. Вот более распространенная форма этой команды:
Ее выполняют, например, после сравнения чисел с плавающей точкой, чтобы ассемблерный код смог проинспектировать флаги состояния. Вот более распространенная форма этой команды:
* vmrs(Rd, FPSCR): Rd = FPSCR
* vmrs(Rd, FPSCR): Rd = FPSCR
Строка 348: Строка 359:
* vstr(Sd, [Rn, смещение]): [Rn + смещение] = Sd
* vstr(Sd, [Rn, смещение]): [Rn + смещение] = Sd


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


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


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


===Преобразование между целым числом или числом с плавающей точкой===
===Преобразование между целым числом или числом с плавающей точкой===
Строка 366: Строка 377:
* label(INNER1)
* label(INNER1)


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


===Команды для встраивания данных===
===Команды для встраивания данных===
Строка 374: Строка 385:
* data(размер, d0, d1 .. dn)
* data(размер, d0, d1 .. dn)


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


<syntaxhighlight lang="python" enclose="div">
data(1, 2, 3, 4)
data(1, 2, 3, 4)
data(4, 2, 100000)
data(4, 2, 100000)
</syntaxhighlight>


Данные, чей размер превышает один байт, сохранятся в памяти в прямом порядке байтов.
Данные, чей размер превышает один байт, сохранятся в памяти в прямом порядке байтов.
Строка 383: Строка 396:
* align(nBytes)
* align(nBytes)


Эта команда выравнивает следующую инструкцию по размеру, заданному в аргументе nBytes. Команды ARM Thumb-2 должны быть выровнены под 2 байта. Следовательно, после директивы data()рекомендуется всегда писать команду align(2). Благодаря этому код, который будет следовать дальше, будет запускаться безотносительно размера данных в массиве, созданном с помощью data().
Эта команда выравнивает следующую инструкцию по размеру, заданному в аргументе ''nBytes''. Команды [[ARM Thumb-2]] должны быть ''выровнены под 2 байта''. Следовательно, после директивы ''data()'' рекомендуется всегда писать команду ''align(2)''. Благодаря этому код, который будет следовать дальше, будет запускаться безотносительно размера данных в массиве, созданном с помощью ''data()''.
 
 
 
<syntaxhighlight lang="python" enclose="div">


=См.также=
=См.также=

Версия от 16:43, 1 августа 2020

Перевод: Максим Кузьмин
Проверка/Оформление/Редактирование: Мякишев Е.А.


Ассемблерная вставка для архитектур 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().

См.также

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