3. Operações sobre valores numéricos¶
Nesta secção são realizadas operações sobre valores numéricos constantes ou valores em variáveis. As variáveis são representadas em registos do processador.
3.1. Afetação¶
Considerando que as variáveis a e b se encontram representadas nos registos R0 e R1, afetar a com b corresponde a executar a instrução mov r0, r1 que copia o conteúdo do registo R1 para R0.
int16_t a, b;
a = b;
|
;a = r0 b = r1
mov r0, r1
|
3.2. Afetação com constante¶
As constantes podem ser escritas nas linguagens de programação em diversas bases de numeração, utilizando-se para cada base uma notação sintática própria. As bases e notações mais usuais estão representadas na Tabela 3.2
Base de numeração |
Esquema sintático |
Exemplo |
|---|---|---|
decimal |
d-- (d:1..9; restantes 0..9) |
123 |
binária |
0b-------- (0..1) |
0b00110110 |
octal |
0-- (0..7) |
023 (dezanove) |
hexadecimal |
0x-- (0..7,a..f,A..F) |
0xff |
O P16 dispõe de duas instruções para carregamento de valores constantes em registo: a instrução mov rd, #constant que carrega uma constante representada a 8 bits na parte menos significativa do registo e zero na parte mais significativa; a instrução movt rd, #constant que carrega um valor de 8 bits na parte mais significativa do registo e mantém o conteúdo da parte menos significativa.
Para carregar valores numéricos entre 0 e 255 pode utilizar-se apenas uma instrução mov. Para valores superiores até 0xffff, utiliza-se uma sequência mov - movt.
(a) |
int16_t a;
a = 40;
|
;a = r0
mov r0, #40
|
(b) |
int16_t a;
a = -2;
|
;a = r0
mov r0, #0xfe
movt r0, #0xff
|
(c) |
int16_t a;
a = 0x1234;
|
;a = r0
mov r0, #0x34
movt r0, #0x12
|
(d) |
#define VALUE -2024
int16_t a;
a = VALUE;
|
;a = r0
.equ VALUE, -2024
mov r0, #VALUE & 0xff
movt r0, #VALUE >> 8
|
Tabela 3.3 (a) – carregamento de valor positivo inferior a 256; utiliza-se apenas uma instrução mov porque esta instrução coloca também a parte alta a zero.
Tabela 3.3 (b) – carregamento de um valor negativo. -2 é representado em código dos complementos a 16 bits por 0xfffe. A instrução mov carrega 0xfe na parte baixa de R0 e a instrução movt carrega a 0xff na parte alta.
Tabela 3.3 (c) – a instrução mov carrega o valor 0x34 na parte baixa de R0 e zero na parte alta. Sendo o valor da constante superior a 256, é necessária a instrução movt para carregar 0x12 na parte alta de R0 e assim formar o valor 0x1234 em R0.
Tabela 3.3 (d) – exemplifica uma programação genérica para qualquer valor numérico
no domínio de representação do tipo int16_t ou uint16_t.
A diretiva .equ VALUE, -2000 significa que no texto do programa,
onde aparece VALUE pode ler-se -2000.
Este valor tem uma representação a 16 bits equivalente a 0xf830.
A expressão VALUE & 0xff é igual a 0x30 e a expressão VALUE >> 8 é igual 0xf8.
Note que a ordem das instruções mov e movt não pode ser invertida.
Na linguagem assembly do P16 uma constante é representada a 16 bits. Para efeitos de operações é considerado um valor do conjunto dos números naturais de 0 a 65535.
No exemplo Tabela 3.3 (d) na expressão -2024 >> 8, o valor -2024 é representado em binário por 1111’1000’0001’1000 e operado como um número natural. O resultado obtido é 0000’0000’1111’1000.
Encarando 0xf818 como número natural, 0xf818 >> 8 = 0x00f8. Encarando 0xf818 como número relativo, 0xf818 >> 8 = 0xfff8.
3.3. Operações aritméticas¶
+ adição |
* multiplicação |
% resto da divisão inteira |
++ incremento |
- subtração |
/ divisão |
-- decremento |
3.3.1. Adição¶
(a) |
int16_t a, b;
a = a + b;
|
; a = r0 b = r1
add r0, r0, r1
|
(b) |
uint16_t a, b;
a = a + b;
|
; a = r0 b = r1
add r0, r0, r1
|
Na Tabela 3.5 apresenta-se a programação da adição de variáveis – no caso (a) variáveis com números relativos e no caso (b) variáveis com números naturais. Ao nível da máquina ambas as operação são realizadas exatamente da mesma forma pela instrução add rd, rn, rm. A diferença está na forma como se interpretam os operandos e os resultados.
No caso dos números naturais, os valores são representados diretamente em binário. Nos registos apenas podem ser representados valores no domínio \(0\) a \(2^{16} - 1\). Se o resultado ultrapassar este domínio é assinalado arrasto na flag Carry, que fica com valor 1.
No caso dos números relativos, os valores são representados em código dos complementos. Nos registos apenas podem ser representados valores no domínio \(-2^{15}\) a \(+2^{15} - 1\). No caso do resultado ultrapassar este domínio, é assinalado erro na flag Overflow, que fica com o valor 1. Isso pode aconteccer se os operandos forem ambos positivos e o resultado for superior a \(+2^{15} - 1\) ou ambos negativos e o resultado for inferior a \(-2^{15}\).
3.3.2. Subtração¶
(a) |
uint16_t a, b;
a = a - b;
|
; a = r0 b = r1
sub r0, r0, r1
|
(b) |
int16_t a, b;
a = a - b;
|
; a = r0 b = r1
sub r0, r0, r1
|
Na Tabela 3.6 apresenta-se a programação da subtração de variáveis.
A instrução sub rd, rn, rm afeta o registo rd com o valor do registo rn menos o valor do registo rm. Além disso afeta também a flag Carry com a informação de arrasto.
Para interpretar o funcionamento da instrução SUB, pode aplicar-se o seguinte modelo: o registo rd recebe a soma do valor do registo rn com o complemento para \(2^{16}\) do valor do registo rm e a flag Carry recebe o arrasto produzido por esta adição.
No caso do valor de rn ser maior que o valor de rm, a operação de subtração não produziria arrasto (borrow). Nesta relação de valores, a adição do complemento de rm com rn produz arrasto (carry) e a flag Carry ficará com 1.
Se, pelo contrário, o valor de rn for menor que o valor de rm, a operação de subtração produziria arrasto (borrow), mas a flag Carry ficará com 0.
3.3.3. Expressão com adição e subtração¶
int16_t a, b, c, d;
a = c + b - d;
|
; a = r0 b = r1 c = r2 d = r3
add r0, r2, r1
sub r0, r0, r3
|
A instrução add r0, r2, r1 adiciona as variáveis c e b (R2 e R1, respetivamente)
e deixa o resultado intermédio em a (R0).
A instrução sub r0, r0, r3 subtrai a variável d (R3) do resultado intermédio em R0
e coloca o resultado final em a (R0).
3.3.4. Adição a 32 bits¶
int32_t a, b, c;
a = b + c;
|
; a = r1:r0 b = r3:r2 c = r5:r4
add r0, r2, r4
adc r1, r3, r5
|
Os valores representados a 32 bits são guardados no processador em dois registos. A variável a é guardada nos registos R0 e R1, com a parte menos significativa em R0 e a mais significativa em R1. As variáveis c e d são guardadas nos registos R3:R2 e R5:R4, de modo semelhante.
A operação de adição das variáveis b e c é realizada em dois passos.
No primeiro passo a instrução add r0, r2, r4 adiciona
as partes menos significativas das variáveis a e b
afetando R0 com o resultado e a flag Carry com o arrasto.
No segundo passo a instrução adc r1, r3, r5 adiciona as partes mais significativas
das variáveis com o arrasto produzido na adição anterior.
A instrução ADC também afeta a flag Carry com o arrasto produzido. O que permite utilizar sucessivamente esta instrução na operação parcial de valores representados com maior número de bits.
3.3.5. Subtração a 32 bits¶
int32_t a, b, c;
c = a - b;
|
; a = r1:r0 b = r3:r2 c = r5:r4
sub r4, r0, r2
sbc r5, r1, r3
|
À semelhança da adição a 32 bits, na subtração a 32 bits é utilizada a combinação das instruções SUB e SBC.
A instrução sub r4, r0, r2 opera as partes menos significativas subtraindo R2 a R0.
R4 é afetado com a diferença e a flag C com informação de arrasto (ver Subtração).
A instrução sbc r5, r1, r3 opera as partes mais significativas, subtraindo R3 + (1 - C) a R1.
R5 é afetado com a diferença e a flag C com informação de arrasto,
como na instrução SUB.
Na instrução sbc rd, rn, rm, o valor a subtrair ao valor do registo rn depende da flag C. Se C for 0, subtrai o valor do registo rm menos 1; se C for 1 subtrai o valor do registo rm.
A instrução SBC opera segundo o seguinte modelo matemático:
\(rd = rn - rm - 1 + C = rn + (2^{16} - rm) - 1 + C = rn + (not(rm) + 1) - 1 + C = rn + not(rm) + C\)
3.3.6. Multiplicação e divisão¶
O P16 não dispõe de instruções de multiplicação ou divisão. Estas operações terão que ser realizadas programaticamente, utilizando as outras instruções.
Exemplos de programação destas operações para o P16 são apresentados no capítulo Exemplos nas secções Multiplicação e Divisão.
3.4. Operações bit-a-bit (bitwise)¶
3.4.1. Deslocar à direita¶
Deslocar um valor para a direita equivale a dividir esse valor por dois elevado ao número de posições deslocadas.
a >> 3 é equivalente a \(a / 2^3 = a / 8\)
As instruções para operar deslocações à direita são LSR e ASR. A primeira insere o valor zero nas posições de maior peso – destina-se a operar números naturais. A segunda mantém o valor do bit de maior peso (bit de sinal) – destina-se a operar números relativos. O último bit de menor peso a ser deslocado fica retido na flag Carry.
Figura 3.1 Funcionamento da instrução LSR.¶ |
Figura 3.2 Funcionamento da instrução ASR.¶ |
(a) |
uint16_t a;
a = a >> 1;
|
; a = r0
lsr r0, r0, #1
|
(b) |
int16_t a;
a = a >> 1;
|
; a = r0
asr r0, r0, #1
|
(c) |
uint32_t a;
a = a >> 1;
|
; a = r1:r0
lsr r1, r1, #1
rrx r0, r0
|
(d) |
uint32_t a;
a = a >> 4;
|
; a = r1:r0
lsr r0, r0, #4
lsl r2, r1, #(16 – 4)
add r0, r0, r2
lsr r1, r1, #4
|
3.4.2. Deslocar à esquerda¶
Deslocar um valor para a esquerda equivale a multiplicar esse valor por dois elevado ao número de posições deslocadas.
a << 5 é equivalente a \(a * 2^5 = a * 32\)
A instrução para operar deslocações à esquerda é LSL. O último bit de maior peso a ser deslocado fica registado na flag Carry. Insere zero no(s) bit(s) de menor peso.
Figura 3.3 Funcionamento da instrução LSL.¶
(a) |
uint16_t a;
a = a << 1;
|
; a = r0
lsl r0, r0, #1
|
(b) |
int16_t a;
a = a << 1;
|
; a = r0
lsl r0, r0, #1
|
(c) |
uint32_t a;
a = a << 1;
|
; a = r1:r0
lsl r0, r0, #1
adc r1, r1, r1
|
(d) |
uint32_t a;
a = a << 4;
|
; a = r1:r0
lsl r1, r1, #4
lsr r2, r0, #(16 - 4)
add r1, r1, r2
lsl r0, r0, #4
|
3.4.3. Rodar¶
Rodar uma palavra para a direita significa inserir nas posições de maior peso, os bits que saem das posições de menor peso; rodar uma palavra para a esquerda significa inserir nas posições de menor peso os bits que saem das posições de maior peso.
A instrução para rodar um valor é ROR. Esta instrução roda para o conteúdo de um registo para a direita, o número de posições indicado.
Figura 3.4 Funcionamento da instrução ROR.¶
No P16 não existe instrução específica para rodar à esquerda. O efeito de rodar à esquerda pode ser obtido rodando à direita um número complementar de posições. Veja-se o segundo exemplo da Tabela 3.12.
Rodar o valor de R0 três posições para a direita. |
ror r0, r0, #3
|
Rodar o valor de R0 cinco posições para a esquerda. |
ror r0, r0, #(16 – 5)
|
3.4.4. Deslocar um número variável de posições¶
O P16 não dispõe de instrução que permita deslocar o conteúdo de um registo um número variável de posições. O terceiro parâmetro das instruções de deslocamento, que define o número de posições a deslocar, é sempre uma constante.
Para deslocar um número variável de posições é necessário elaborar um programa.
Na Tabela 3.13 (b) apresenta-se uma solução que realiza um número de iterações igual ao número de posições a deslocar (valor do registo R1), deslocando uma posição em cada iteração (linha 5).
A solução apresentada na Tabela 3.13 (c) executa o deslocamento em
quatro passos (instruções lsl r0, r0, #X (linhas 4, 8, 12 e 16).
Em cada passo desloca condicionalmente uma, duas, quatro ou oito posições,
perfazendo um máximo de quinze posições.
O número de posições a deslocar é representado pelos quatro bits de menor peso de R1. Por exemplo, no terceiro passo (linha 10 a 12) é testado o bit de peso dois de R1. Se este bit for 1, R0 é deslocado quatro posições. Se for 0, R0 não é deslocado.
O programa da Tabela 3.13 (b) demora a executar um tempo igual ao de 2 + 5 * n instruções enquanto o programa da Tabela 3.13 (c) demora o tempo igual ao de 8 a 12 instruções.
int16_t a, n;
a <<= n;
(a) |
1;a = r0 n = r1
2 add r1, r1, #0
3 bzs shift_end
4shift:
5 lsl r0, r0, #1
6 sub r1, r1, #1
7 bzc shift
8shift_end:
(b) |
1;a = r0 n = r1
2 lsr r1, r1, #1
3 bcc shift_1
4 lsl r0, r0, #1
5shift_1:
6 lsr r1, r1, #1
7 bcc shift_2
8 lsl r0, r0, #2
9shift_2:
10 lsr r1, r1, #1
11 bcc shift_4
12 lsl r0, r0, #4
13shift_4:
14 lsr r1, r1, #1
15 bcc shift_8
16 lsl r0, r0, #8
17shift_8:
(c) |
3.4.5. Afetar um bit com 1¶
Afetar o bit de peso três da variável a com o valor 1, mantendo o valor dos restantes bits.
A instrução mov r1, #(1 << 3) coloca o valor 0000 0000 0000 1000 em R1.
A instrução orr r0, r0, r1 realiza a operação disjunção (or)
entre os bits das mesmas posições de R0 e R1.
O resultado é o valor original de R0 quando operado com 0 em R1 – elemento neutro –
ou o valor 1 quando operado com 1 em R1 – elemento absorvente.
uint16_t a;
a |= 1 << 3;
|
; a = r0
mov r1, #(1 << 3)
orr r0, r0, r1
|
3.4.6. Afetar um bit com 0¶
Afetar o bit de peso doze da variável a com o valor 0, mantendo o valor dos restantes bits.
As instruções mov r1, #(~(1 << 12) & 0xff) e movt r1, #(~(1 << 12) >> 8)
colocam o valor 1110 1111 1111 1111 em R1.
A instrução and r0, r0, r1 realiza a operação conjunção (and)
entre os bits das mesmas posições de R0 e R1.
O resultado é o valor original de R0 quando operado com 1 em R1 – elemento neutro –
ou o valor 0 quando operado com 0 em R1 – elemento absorvente.
uint16_t a;
a &= ~(1 << 12);
|
; a = r0
mov r1, #(~(1 << 12) & 0xff)
movt r1, #(~(1 << 12) >> 8)
and r0, r0, r1
|
3.4.7. Afetar um bit de uma variável com o bit de outra variável¶
Afetar o bit de peso quatro da variável a com o valor do bit de peso treze da variável b, mantendo os restantes bits.
uint16_t a, b;
uint16_t tmp = b >> (13 - 4);
tmp &= (1 << 4);
a &= ~(1 << 4);
a |= tmp;
|
; a = r0 b = r1 tmp = r2
lsr r2, r1, #(13 - 4)
mov r3, #(1 << 4)
and r2, r2, r3
mvn r3, r3
and r0, r0, r3
orr r0, r0, r2
|
3.4.8. Multiplicar por constante¶
A multiplicação de uma variável por uma constante pode ser realizada, sem recurso a instrução de multiplicação ou a programa genérico de multiplicação. Veja-se o seguinte exemplo:
\(a * 21 = a * (16 + 4 + 1) = a * 16 + a * 4 + a * 1\)
A constante 21 é decomposta em parcelas de valor igual a potências de dois. As multiplicações parciais são realizadas por instruções de deslocamento.
uint16_t a, b;
uint16_t b = a * 21;
|
; a = r0 b = r1
mov r1, r0 ; a * 1
lsl r0, r0, #2
add r1, r1, r0 ; + a * 4
lsl r0, r0, #2
add r1, r1, r0 ; + a * 16
|
3.5. Conversão entre tipos numéricos¶
A representação dos tipos numéricos diferem entre si no número de bits e na representação de sinal. Existe por vezes a necessidade de alterar a representação de valores. Por exemplo, afetar um valor guardado numa variável representada a oito bits (int8_t) a uma variável representada a dezasseis bits (int16_t), ou o contrário.
3.5.1. Conversão sem perda de informação¶
Na conversão de tipo cujo domínio de representação está contido no domínio de representação do tipo destino – representação com menos bits para representação com mais bits – não há perda de informação. Para manter a mesma representação numérica, os bits de maior peso recebem o valor zero no caso de valores naturais ou o valor do bit de sinal no caso de valores relativos.
Nos exemplos da Tabela 3.18 a conversão de 8 para 16 bits dá-se ao carregar as constantes nos registos do processador. Como o P16 realiza apenas operações a 16 bits, os valores originalmente representados a 8 bits devem ser representados a 16 bits ao serem carregados nos registos do processador.
Nos casos (a) e (b) da Tabela 3.18, o aumento para 16 bits
consiste em acrescentar zero na parte alta de R0.
Esse resultado é obtido pela funcionamento das instruções mov r0, #10 e mov r0, #22
que afetam a parte alta de R0 com zero.
Nos casos (c) e (d) da Tabela 3.18, o aumento para 16 bits consiste em propagar o bit de sinal para a parte alta do destino. No caso (c) a parte alta de R0 recebe 0xff porque se trata de carregar a constante -3. No caso (d) a parte alta da variável, representada em R2, recebe em todas as posições um valor igual ao bit de maior peso de R0 (bit de sinal do valor original).
(a) |
uint8_t a;
uint16_t b;
a = 10;
b = a;
|
; a = r0 b = r1
mov r0, #10
mov r1, r0
|
(b) |
uint8_t a;
int16_t b;
a = 22;
b = a;
|
; a = r0 b = r1
mov r0, #22
mov r1, r0
|
(c) |
int8_t a;
int16_t b;
a = -3;
b = a;
|
; a = r0 b = r1
mov r0, #-3
movt r0, #0xff
mov r1, r0
|
(d) |
int16_t a;
int32_t b;
b = a;
|
; a = r0 b = r2:r1
mov r1, r0
mov r2, r0
asr r2, r2, #15
|
3.5.2. Conversão com perda de informação¶
Na conversão de tipo cujo domínio de representação é superior ao domínio de representação do tipo destino, pode haver perda de informação. Para o evitar cabe ao programador garantir que o valor a converter é representável no domínio do tipo destino.
(a) |
uint16_t a;
uint8_t b;
b = a;
|
; a = r0 b = r1
mov r2, #0xff
and r1, r0, r2
|
(b) |
int32_t a;
int16_t b;
b = a;
|
; a = r1:r0 b = r2
mov r2, r0
|
3.6. Exercícios¶
Este conjunto de exercícios destina-se a exercitar a programação, em linguagem assembly do P16, de operações sobre valores numéricos.
O objetivo de cada exercício é programar, em linguagem assembly, o cálculo da expressão definida em linguagem C.
Concretize as variáveis como registos do processador.
int a = 3; int b = 4; int c = a - b + 20;
int32_t a = 3; int32_t b = 4; int32_t c = a + b;
int a = 3; int b = a << 2;
uint8_t a = 24; uint8_t b = a >> 2;
int16_t a = -40; int16_t b = a >> 2;
int a = 3; int b = a * 2;
int32_t a = 65535; int32_t b = a * 2;
int a = 30; int b = a / 2;
int a = 35; int b = a % 4;
int a = 35; int b = a & 2;
int a = 35; int b = a | 4;
int a = 35; int b = a * 27;