6. Valores em memória

6.1. Modelo da memória

A arquitetura do P16 endereça ao espaço de memória com palavras de 16 bits, o que proporciona um alcance a \(2^{16}\) (65536 ou 64 Ki) posições de memória. Cada posição de memória armazena uma palavra de oito bits – um byte, sendo esta a unidade mínima de endereçamento.

A Figura 6.1 representa o espaço de endereçamento. Do lado esquerdo os endereços das posições de memória, que vão de 0x0000 a 0xffff. No interior, os conteúdos das posições de memória. Por exemplo, a posição de memória com endereço 0xFFF9 contém o byte de valor 0xA7.

_images/p16-memory-space.png

Figura 6.1 Representação do espaço de endereçamento do P16.

O P16 é classificado como um processador de 16 bits, porque processa palavras formadas por 16 bits – também designadas por words. Armazenada em memória, uma word ocupa duas posições de memória consecutivas e diz-se alinhada se ocupar como primeira posição, no sentido crescente dos endereços, uma posição de endereço par – o valor do bit de menor peso do endereço ser zero.

A Figura 6.1 mostra o valor numérico 262 (0x0106), representado numa palavra de 16 bits, armazenada em memória. O byte de menor peso da palavra (0x06) ocupa a posição de memória de endereço 0xFFFC e o byte de maior peso da palavra (0x01) ocupa a posição de memória de endereço 0xFFFD.

O posicionamento na memória, em que a parte de menor peso da palavra, ocupa um endereço menor e a parte de maior peso, ocupa um endereço maior, designa-se por little-endian. A situação contrária designa-se por big-endian.

6.2. Acesso a valores em memória

No P16, o acesso a valores em memória faz-se utilizando as instruções LDR e STR. A instrução LDR copia dados da memória para os registos e a instrução STR copia dados dos registos para a memória. Os parâmetros destas instruções são um registo e um endereço de memória. A especificação do endereço da posição de memória é feita através de registos – designa-se por endereçamento indireto. Este modo de endereçamento consiste em utilizar o conteúdo de registos como endereço de memória.

ldr    rd, [rn, ...]
str    rs, [rn, ...]

Na instrução LDR, o registo rd é quem vai receber os dados a ler da memória. Na instrução STR, o registo rs é quem vai fornecer os dados a escrever na memória. Em ambas as instruções, o endereço da memória é definido pela expressão entre parêntesis retos – [rn, …]. O endereço é calculado pela adição do conteúdo de rn com um segundo componente que pode ser um registo – [rn, rm] ou uma constante – [rn, #constant].

O endereçamento indireto em que o endereço é definido por duas componentes, designa-se por endereçamento indexado. A primeira componente tem o nome de base e a segunda componente tem o nome de índice. A base é sempre um registo – rn –, o índice pode ser um registo – rm – ou uma constante – #constant.

Este esquema de endereçamento é adaptado ao acesso a valores em array, em que o registo base recebe o endereço inicial do array e a segunda componente é utilizada como índice do array (daí a designação de índice).

Nos exemplos seguintes, vão ser realizados acessos a variáveis simples, basta utilizar a componente base. Vai ser considerada a variante de instrução com índice constante igual a zero – [rn, #0] – situação em que se pode usar a sintaxe ldr rd, [rn] ou str rd, [rn].

Ler variável de 8 bits

Tabela 6.1 Ler a variável x, de 8 bits, para R0.
uint8_t z, x = 23;

z = x;
;r0 - z
;r1 - address of x
ldrb    r0, [r1]
_images/ldrb.png

A variável x, do tipo uint8_t, representada em memória com 8 bits, é alojada na posição de endereço 0x0005.

No registo R1 foi previamente carregado o endereço da variável x (endereço 0x0005).

A instrução ldrb  r0, [r1] copia o conteúdo da posição de memória de endereço 0x0005 – o valor 0x23 – para os 8 bits menos significativos de R0 e afeta os 8 bits mais significativos com zero. O valor da variável x fica neste momento representado com 16 bits no registo R0.

Ler variável de 16 bits

Tabela 6.2 Ler a variável y, de 16 bits, para R0.
uint16_t w, y = 0x3e7a;

w = y;
;r0 - w
;r1 - address of y
ldr    r0, [r1]
_images/ldr.png

A variável y, do tipo uint16_t, representada em memória com 16 bits, ocupa as posições de endereços 0x0006 e 0x0007.

No registo R1 foi previamente carregado o endereço da variável y (endereço 0x0006).

A instrução ldr  r0, [r1] copia dois bytes da memória para o registo R0. O conteúdo da posição de memória de endereço 0x0006 – valor 0x7a – para os 8 bits menos significativos de R0 e o conteúdo da posição de memória de endereço 0x0007 – valor 0x3e – para os 8 bits mais significativos (posicionamento little endian).

Escrever em variável de 8 bits

Tabela 6.3 Escrever o valor 0x9b na variável x.
uint8_t  x;

x = 0x9b;
;r1 - address of x
mov    r0, #0x9b
strb   r0, [r1]
_images/strb.png

A variável x, do tipo uint8_t, representada em memória com 8 bits, é alojada na posição de endereço 0x0005.

O endereço da variável x (endereço 0x0005) foi previamente carregado em R1.

A instrução strb  r0, [r1] copia o valor dos 8 bits menos significativos de R0 (valor 0x9b), para a posição de memória de endereço 0x0005. Esta instrução é indiferente ao valor presente nos 8 bits mais significativos de R0.

Escrever em variável de 16 bits

Tabela 6.4 Escrever o valor 0x67a4 na variável y.
uint16_t y;

y = 0x67a4
;r1 - address of y
mov   r0, 0xa4
movt  r0, 0x67
str   r0, [r1]
_images/str.png

A variável y é alojada em memória nas posições de memória 0x0006 e 0x0007.

O endereço da variável y (endereço 0x0006) foi previamente carregado em R1.

A instrução str  r0, [r1] copia o valor dos 8 bits menos significativos de R0 (valor 0xa4) para a posição de memória de endereço 0x0006 e o valor dos 8 bits mais significativos de R0 (valor 0x67) para a posição de memória de endereço 0x0007 – posicionamento little endian.

6.3. Valores em array

Arrays são sequências de variáveis do mesmo tipo, alojadas em posições de memória contíguas. As posições do array são definidas pelo índice. O índice 0 corresponde ao endereço mais baixo e os restantes índices a endereços mais altos. Os acessos aos elementos do array são realizados pelas instruções de endereçamento baseado e indexado:

ldr rd, [rn, rm]   ldr rd, [rn, #imm4]
str rd, [rn, rm]   str rd, [rn, #imm4]

se se tratar de array de words ou

ldrb rd, [rn, rm]   ldrb rd, [rn, #imm3]
strb rd, [rn, rm]   strb rd, [rn, #imm3]

se se tratar de um array de bytes.

Estas instruções determinam o endereço de acesso à memória somando a rn uma segunda componente: rm ou uma constante (imm4 ou imm3). Em rn carrega-se o endereço da primeira posição do array e através da segunda componente (rm, imm4 ou imm3) define-se a posição a que se pretende aceder.

imm4 e imm3 representam valores constantes representados com quatro ou três bits, respetivamente.

Tabela 6.5 Acesso a array de bytes.
uint8_t array[] = {2, 0x23, 0x54, 0x10};
uint16_t a;

for (uint16_t i = 0; i < 10; ++i)
    a += array[i]
; r0 - address of array
; r1 - i r2 - a
    mov   r1, #0
    mov   r4, #10
    b     for_cond
for:
    ldrb  r3, [r0, r1]
    add   r2, r2, r3
    add   r1, r1, #1
for_cond:
    cmp   r1, r4
    blo   for
_images/array_bytes.png

No programa (b) da Tabela 6.5 assume-se que o endereço inicial do array foi previamente carregado no registo R0 (endereço 0x4078). Cada posição deste array ocupa uma posição de memória. O endereço de array[i] é determinado pela instrução ldrb  r3, [r0, r1] adicionando o índice i, em R1, ao endereço base do array em R0.

Tabela 6.6 Acesso a array de words.
int16_t array[] = {2, 0x5022, 0x56, 0x1011};
int16_t a;

for (uint16_t i = 0; i < 10; ++i)
    a += array[i]
; r0 - address of array
; r1 - i r2 - a
    mov   r1, #0
    mov   r4, #10
    b     for_cond
for:
    add   r3, r1, r1
    ldr   r3, [r0, r3]
    add   r2, r2, r3
    add   r1, r1, #1
for_cond:
    cmp   r1, r4
    blo   for
_images/array_words.png

No programa da Tabela 6.6, os elementos do array são valores representados a 16 bits – ocupam duas posições de memória. O acesso ao elemento array[i] é realizado pela instrução ldr  r3, [r0, r3] que acede à posição de memória que resulta da soma de R0 com R3. Assume-se que R0 tem o endereço da primeira posição do array (endereço 0x4076) e R3 a distância, em posições de memória, entre o endereço de array[i] e o endereço de array[0]. Esta distância é definida pela instrução add  r3, r1, r1 que multiplica o índice i, em R1, pela dimensão de cada elemento do array (2 bytes).

6.4. Carregamento de valores com aumento de bits

Valores dos tipos int8_t ou uint8_t são representados em memória com 8 bits. Como o P16 realiza operações de dados a 16 bits, estes valores ao serem carregados em registo, para serem posteriormente operados, devem ser convertidos para representação a 16 bits.

No caso do tipo uint8_t, como a instrução LDRB coloca a parte alta do registo a zero, nada mais há a fazer. No caso do tipo int8_t, é necessário propagar o valor do bit de sinal (posição 7) para todos os bits da posição 8 até à posição 15. Para tal pode usar-se o seguinte código depois da instrução LDRB:

lsl  r0, #8
asr  r0, #8

Com LSL o bit de sinal (posição 7) é deslocado para a posição 15 e com ASR é recolocado na posição 7. A instrução asr  r0, #8 ao deslocar R0 para a direita mantém na posição 15 o valor original e preenche as posições até à 7 com esse valor.

6.5. Carregamento de endereço em registo

O programa da Tabela 6.7 incrementa a variável x alojada em memória. Ao nível da máquina, as operações a realizar são: ler o conteúdo da variável de memória para registo; incrementar esse registo; voltar a escrever esse registo na variável em memória.

Tabela 6.7 Carregamento de endereço em registo.
uint8_t x = 55;

x++;
 1    .data
 2x:
 3    .byte  0x55
 4
 5    .text
 6    ldr    r1, x_addr
 7    ldrb   r0, [r1]
 8    add    r0, r0, #1
 9    strb   r0, [r1]
10
11x_addr:
12    .word  x

A variável x é definida em linguagem assembly pela label x: seguida da diretiva .byte 0x55, que significa reservar uma posição de memória inicializada com o valor 0x55 (linhas 2 e 3). A diretiva .data indica uma zona de memória para variáveis.

Em linguagem assembly uma label tem um valor associado que é o endereço de memória assinalado pela label. No exemplo da Tabela 6.7, a label x tem um valor associado que é o endereço da posição de memória assinalada por x: (a que contém 0x55).

Para aceder à variável x – copiar o seu conteúdo para registo ou alterar o seu conteúdo com o valor de um registo – utilizam-se, respetivamente, as instruções ldrb  r0, [r1] e strb  [r0, [r1] (ver secção Acesso a valores em memória). A utilização destas instruções implica carregar previamente em R1, o endereço de x.

A solução geral para carregar endereços em registos passa por utilizar a instrução ldr rd, label. Esta instrução copia um valor expresso a 16 bits, armazenado em memória, no endereço definido por label, para o registo rd.

A instrução ldr  r1, x_addr carrega em R1 a palavra de 16 bits alojada em memória na posição assinalada pela label x_addr:. Esse conteúdo é o endereço da variável x, definido pela diretiva .word x, que reserva duas posições de memória inicializadas com o valor da label x.

A instrução ldr rd, label usa um método de endereçamento relativo ao PC, para definir o endereço da posição de memória especificada por label. Esse endereço é obtido adicionando o valor atual do PC à constante codificada no campo imm6 do código binário da instrução (ver Figura 6.2). Este campo codifica a distância, no espaço de endereçamento, a que label se encontra da instrução ldr rd, label, em número de words (palavras de 16 bits), no sentido crescente dos endereços.

_images/ldr_label.png

Figura 6.2 Carregamento em registo do endereço de uma variável

A instrução ldr  r1, x_addr carrega 0x6037 em R1 (endereço da variável x). Este valor está armazenado em memória no endereço 0x4022 (posição indicada por x_addr:). Esta instrução determina o valor 0x4022 adicionando ao valor atual do PC (0x400a) o dobro do campo imm6 (0xb) (0x4022 = 0x400a + 0x0b * 2). Na fase de codificação binária do programa, o valor imm6 é calculado como metade da diferença entre o endereço de x_addr e o valor atual do PC ((0x4022 – 0x400a) / 2). Na fase de execução de uma instrução, o PC contém o endereço da instrução seguinte. A instrução ldr  r1, x_addr ocupa o endereço 0x4008 mas na altura em que está a ser executada o valor do PC é 0x400a.

6.6. Exercícios

Este conjunto de exercícios destina-se a exercitar a programação, em linguagem assembly do P16, das transferências de dados entre a memória e os registos do processador.

Os dados podem estar armazenados em variáveis podem ser isoladas ou em array.

O objetivo de cada exercício é traduzir para assembly, o trecho de código apresentado em linguagem C.

  1. Reformular o programa realizado no exercício da multiplicação na Secção 5.6, assumindo que agora os operandos e os resultados estão armazenados em memória.

  2. Reformular o programa realizado no exercício da divisão na Secção 5.6, assumindo que agora os operandos e os resultados estão armazenados em memória.

  3. bool found = false;
    char v[] = {'1', '3', 'd', 'i', 'o', '8', '6'};
    char x = 'o';
    
    for (int i = 0; i < 7; ++i)
        if (v[i] == x)
            found = true;
    
  4. char name[] = "manuel joaquim silva";
    int i = 0;
    
    while (name[i] != 0) {
        name[i] -= ' '
        while (name[i] != 0 && name[i] != ' ')
            i++;
        if (name[i] == ' ')
            i++;
    }
    
  5. uint16_t vector[] = {20, 30, 100, 200, 40, 6};
    uint16_t bias = 100;
    
    for (int i = 0; i < 6; ++i)
        vector[i] += bias;
    
  6. int8_t input[] = {+20, -4, -7, +3, +99, -100, +126};
    int16_t output[10];
    
    for (int i = 0; i < 7; ++i)
        output[i] = input[i];
    

    Este programa converte uma sequência de valores inteiros relativos representados em código dos complementos a 8 bits, numa sequência de representação dos mesmos valores representados a 16 bits.