Estas instruções, denominadas de Single Data Transfer, são codificadas conforme a figura abaixo:
Note que embora sejam apenas duas instruções assembly, LDR e STR, existem uma grande quantidade de opções. Como sempre, o opcode contém uma condição. O bit L diferencia a instrução de Load (carga do registrador a partir da memória) da instrução de Store (armazenamento na memória do conteudo do registrador). Rd indica o registrador que receberá o valor lido (LDR) ou que contém o valor a ser armazenado (STR). O bit B indica se estamos manipulando um word (32 bits) ou byte (8 bits); existem instruções separadas para manipular valores de 16 bits (half-words). O resto dos campos do opcode determinam o endereço da posição de memória a ser manipulada.
Este endereço é determinado por duas partes: a base (que fica no registrador Rn) e o offset (que, conforme o bit I, pode ser um valor imediato de 12 bits ou o conteúdo de um registrador deslocado). O deslocamento do registrador usado para o offset é determinado de forma semelhante ao usado para o valor imediato nas instruções lógicas e aritméticas. O bit U indica se o offset deve ser somado ou subtraído da base.
O bit P determina se a combinação da base com o offset deve ser feita antes (pré-indexação) ou depois (pós-indexação) do acesso à memória. O bit W permite atualizar o registrador base com o valor final do endereço. Quando se especifica a pós-indexação, o ARM atualiza sempre o registrador base, para não alterá-lo deve-se colocar um offset de zero. Na prática, isto significa:
- P = 0, W = 0: normalmente tratado pelo ARM como P = 0, W = 1. (para ser preciso, no modo privelegiado em um processador com hardware de gerenciamento de memória esta combinação faz com que o endereço seja tratado como um endereço de usuário ao invés de endereço de sistema).
- P = 1, W = 0: combina base e offset para determinar o endereço, o registrador base fica inalterado. Útil, por exemplo, para acessar um item de um vetor (base é o endereço inicial e o offset é o deslocamento em relação ao início).
- P = 0, W = 1: usa somente a base para determinar o endereço, após o acesso à memória a base é atualizada com a combinação da base com o offset. Permite implementar em uma única instrução a construção *p++ do C (acessa a posição apontada pelo registrador base e o avança para o próximo item).
- P = 1, W = 1: combina base e offset para determinar o endereço, o registrador base é atualizado para o endereço usado. Permite implementar em uma única instrução a construção *++p do C (avança o registrador base para o próximo item e o acessa).
LDR{cond}{B} Rd,<endereço>
STR{cond}{B} Rd,<endereço>
Onde B indica que é um acesso a byte.
- uma expressão, que resulta em um endereço. O Assembler tenta montar uma instrução que combine um offset ao PC para obter este endereço, se não conseguir gerará um erro. Esta forma é útil para carregar em registradores constantes que estão armazenadas junto ao código.
- uma especificação de endereçamento pré-indexado (combina base e offset antes de acessar a memória):
- [Rn]
- [Rn,#expressão] {!}
- [Rn,{+/-}Rm{,<shift>}] {!}
- uma especificação de endereçamento pós-indexado (combina base e offset depois de acessar a memória):
- [Rn],#expressão
- [Rn],{+/-}Rm{,
}
nestes casos o registrador base é sempre atualizado.
A presença de ! indica que o registrador base deve ser atualizado (W = 1).
; coloca valores conhecidos em R1 e R2
MOV R1,#0
MOV R2,#4
; coloca em R0 o conteúdo da posição 0 da memória
LDR R0,[R1]
; coloca em R0 o conteúdo da posição 4 da memória
LDR R0,[R1,R2]
; coloca em R0 o conteúdo da posição 32 da memória
LDR R0,[R1,R2,LSL #3]
; idem
LDR R0,[R1+#32]
; coloca em R0 o conteúdo da posição 4 da memória
; R1 passa a ser 4
LDR R0,[R1,R2]!
; coloca em R0 o conteúdo da posição 4 da memória
; R1 passa a ser 8
LDR R0,[R1],R2
Reparar que não é possível especificar diretamente um endereço qualquer da memória, pois o offset imediato tem 12 bits e um endereço tem 32 bits. Além disso, já vimos que as instruções lógicas e aritméticas são limitadas quanto aos valores imediatos que podem ser usados. Para carregar em um registrador um endereço ou constante qualquer, é preciso armazená-lo em memória em uma posição que possa ser acessada por um LDR. Isto torna frequente a presença de constantes no meio do código:
...
LDR Rd,X ; usa endereçamento relativo ao PC
...
X .long valor ; em algum lugar próximo
Para facilitar isto, o Assembler permite a seguinte construção:
LDR Rd,=valor
Se possível, o assembler gera uma instrução MOV ou MVN para carregar o valor no registrador. Se isto não for possível, o assembler coloca a constante em um trecho próximo e gera uma instrução LDR usando endereçamento relativo ao PC.
Outras Instruções de Acesso à Memória
As instruções LDRH, LDRSH e STRH permitem carregar e armazenar valores de 16 bits (half word). A instrução LDRSH faz a "extensão do sinal", isto é, preenche os 16 bits mais significativos do registrador com o bit mais significativo do valor de 16 bits, de forma a manter o sinal do valor. O mesmo opcode permite também carregar em um registrador um valor de 8 bits com extensão de sinal (LDRSB). Estas instruções oferecem os mesmos recursos de endereçamento que LDR e STR.
As instruções LDM e STM (Block Data Transfer) permitem carregar ou armazenar um conjunto de registradores em posições contiguas de memória. A codificação destas instruções é apresentada abaixo:
Register list possui 16 bits, um para cada registrador. Os bits com 1 indicam os registradores a serem transferidos. O endereço inicial é obtido a partir de um registrador, que será automaticamente incrementado ou decrementado, antes ou depois de transferir cada registrador. O registrador utilizado pode ou não ser atualizado ao final da transferência.
A codificação em assembly destas instruções é:
LDM{cond}<fd|ed|fa|ea|ia|ib|da|db> Rn{!},<rlist>
STM{cond}<fd|ed|fa|ea|ia|ib|da|db> Rn{!},<rlist>
Onde
- {cond} é a condição em que a instrução será executada.
- FD, ED,..DB determinam os bits P e U. FD, ED, FA e EA são (respectivamente) idênticos a IA, IB, DA e DB; os primeiros são utilizados para documentar quando os registradores estão sendo transferidos para uma pilha.
- ! indica que o registrador Rn deve ser atualizado ao final
; salva todos os registradores em posições
; consecutivas a partir da apontada por R0
STMIA R0,{R0-R15}
; empilha os registradores (exceto R15/PC)
STMFD SP!,{R0-R15}
; Salva na pilha os registradores R1, R3 e R5
STMFD SP!,{R1, R3, R5}
; desempilha os registradores
LDMED SP!,{R0-R14}
3 comentários:
cara, muito legal o Post.
Estudo assembly de x86 a alguns anos, porém asm de ARM ainda é um tanto alienigena pra mim.. porém teus posts sao bastante simplificadores :).
Seria legal explicar um pouco mais sobre como funciona o procedure call em arm.
Abraco!
Muito bom mesmo esse post. Seria legal se vc pudesse postar alguma coisa detalhando o acesso aos pinos de I/O. Gosto de fazer algumas chamadas em assembly para otimizar meu código, porém tenho muita dificuldade com o arm.
Abraço!
"Ricardão" o acesso aos pinos de I/O varia conforme o microcontrolador. Anotei na lista de ideias comentar como é nos microcontroladores baseados em ARM que eu tive contato.
Postar um comentário