10. Estrutura dos programas

Para assegurar a adequada versatilidade na utilização do espaço de endereçamento, de acordo com as caraterísticas dos dispositivos de memória que o populam e as necessidades da aplicação, um programa é organizado por zonas de memória.

Destacam-se três zonas:
  • zona para código binário das instruções;

  • zona para variáveis;

  • e zona para stack.

Estas zonas de memória são designadas na literatura inglesa por segment ou section. Aqui, vais ser utilizado o termo “secção”.

Para concretização deste modelo, definem-se três secções:
  • .text – para o código binário das instruções e dados constantes;

  • .data – para as variáveis que são modificadas durante a execução do programa;

  • .stack – para suporte da execução estruturada com base em rotinas (funções).

_images/section_map_min.png

Figura 10.1 Organização do programa em três secções

10.1. .data

As variáveis são alojadas na secção .data.

/*
uint8_t m = 20, n = 3;

m:
	.byte	20
n:
	.byte	3
p:
	.word	0
q:

10.2. .text

O código binário das instruções do programa, assim com dados auxiliares ao código, são alojados na secção .text.

int main() {
	q = multiply(4, 7);
	p = multiply(m, n);
}
main:

	push	lr

	mov	r0, #4
	mov	r1, #7
	bl	multiply
	ldr	r1, q_addr
	str	r0, [r1]

	ldr	r0, m_addr
	ldrb	r0, [r0]
	ldr	r1, n_addr
	ldrb	r1, [r1]
	bl	multiply
	ldr	r1, p_addr
	str	r0, [r1]

	pop	pc

m_addr:
	.word	m
n_addr:
	.word	n
p_addr:
	.word	p
q_addr:

10.3. .stack

A secção .stack é uma zona de memória para salvaguarda de dados temporários, necessários à execução do programa. O conteúdo inicial desta zona de memória é indiferente.

A diretiva .space na linha 34 da Listagem 10.1 reserva a área de memória cuja dimensão pode ser ajustada através do símbolo STACK_MAX_SIZE definido na linha anterior.

10.4. Ambiente de execução

Os programas aqui apresentados seguem o modelo de execução da linguagem C. A norma define dois modelos de execução: Hosted environment e Freestanding environment. O primeiro para sistemas com sistema operativo e o segundo para outros casos.

No caso da programação para o P16 é usado o modelo Freestanding environment.

Em ambos os modelos, a execução do programa começa com a chamada de uma função principal e termina com o retorno dessa função. Para chamar e receber o retorno da função principal, é criado um código de preparação que além destas ações, realiza as necessárias inicializações.

No modelo Hosted a função principal deve ter uma das seguintes assinaturas:

int main(void);
int main(int argc, char *argv[]);

No modelo Freestanding o nome e tipo da função principal não é definido pela norma, ficando ao critério do implementador. Nestes exemplos de programas vai ser usada:

void main(void);

Após a ação reset, o P16 passa a executar código a partir do endereço 0x0000. Neste endereço inicia-se o código de preparação – linha 6.

A preparação consiste em inicializar o registo SP com o endereço de topo da secção .stack – linhas 10, 14 e 15 da Listagem 10.1. Segue-se a chamada da função main na linha 11.

Após o retorno do programa, a única ação a tomar é garantir que o processamento não se descontrola. Para isso é executada a instrução b    ., que significa saltar para o endereço atual o que resulta na execução repetida desta instrução – linha 12.

Listagem 10.1 Estrutura de código padrão
 1;-------------------------------------------------------------------------------
 2;	Secção .text são alojadas as instruções
 3
 4	.text
 5
 6	b	start
 7	b	.
 8
 9start:
10	ldr	sp, stack_top_addr
11	bl	main
12	b	.
13
14stack_top_addr:
15	.word	stack_top
16
17main:
18
19	;	Código da função main
20	;	e das restantes funções do programa
21
22;-------------------------------------------------------------------------------
23;	Secção .data onde são alojadas as variáveis
24
25	.data
26
27;-------------------------------------------------------------------------------
28;	Secção .stack onde é reservada memória para Stack
29
30	.stack
31
32	.equ    STACK_MAX_SIZE, 1024
33	.space	STACK_MAX_SIZE * 2
34stack_top:

10.5. Localização

A localização e a dimensão das secções no espaço de endereçamento designa-se por mapa de memória. As secções são localizadas pela ordem com que são escritas no ficheiro fonte do programa. O endereço da secção seguinte é o endereço par imediatamente disponível depois do fim da secção anterior. A dimensão de cada secção depende do número de instruções e do número de variáveis e respetivos tipos, definidos no seu interior.

O código completo do programa utilizado como exemplo nesta secção pode ser descarregado daqui: multiply.s.

O comando

$ p16as multiply.s

gera os ficheiros multiply.hex e multiply.lst.

O mapa de memória do programa, que faz parte do ficheiro multiply.lst, pode ser visualizado nas linhas 5 a 7 da multiplylst.

A secção .text começa no endereço 0x0000 por ser a que aparece em primeiro lugar no ficheiro fonte do programa.

O endereço da secção .data é posterior à secção .text porque aparece em segundo lugar. O seu endereço inicial é 0x0044 porque é essa a dimensão da secção anterior.

As instruções b  program e b  . que aparem no início da secção .text são localizadas nos endereços 0x0000 e 0x0002, respetivamente. Estes endereços são fixados pela arquitetura P16, como os pontos de entrada em execução dos acontecimentos reset e interrupção, respetivamente. Razão pela qual o código de preparação não é localizado exatamente no endereço 0x0000.

A instrução b  . no endereço 0x0002 é apenas figurativa. Este programa não está capacitado para realizar processamento de interrupções.

10.5.1. Exercícios

Este conjunto de exercícios destina-se a exercitar a programação, em linguagem assembly do P16.

O objetivo de cada exercício é traduzir para assembly, o código de todo o programa definido em linguagem C, incluindo a definição das variáveis.

  1. int x = 2;
    int y = 3;
    int z;
    
    void main(void)
    {
        z = x + y;
    }
    
  2. uint8_t m = 20, n = 3;
    uint16_t p, q;
    
    int main()
    {
        p = multiply(m, n);
        q = multiply(4, 7);
    }
    
  3. uint16_t q, r, d = 200, e = 10;
    
    int main()
    {
       q = divide(d, e);
       r = divide(43, 7);
    }
    
  4. uint16_t a = 1000;
    uint8_t log2a;
    uint8_t log10a;
    
    int main()
    {
    
        log2a = logarithm(a, 10);
        log10a = logarithm(a, 2);
    }
    
    uint8_t logarithm(uint16_t value, uint8_t base)
    {
        uint8_t log = 0;
        while (value >= base) {
            ++log;
            value = divide( value, base );
        }
        return log;
    }
    
  5. uint8_t exp = 3, base = 4
    uint16_t power1, power2;
    
    int main() {
        power1 = power(base, exp);
        power2 = power(2, 7);
    }
    
    uint16_t power(uint8_t b, uint8_t e)
    {
        uint16_t result = 1;
        while (e > 0) {
            if (result > 0xff)
                return UINT16_MAX;
            result = multiply(result, b);
            e--;
        }
        return result;
    }
    
  6. uint16_t a = 333;
    uint8_t prime1;
    uint8_t prime2;
    
    int main()
    {
        prime1 = is_prime(a);
        prime2 = is_prime(77);
    }
    
    bool is_prime(uint16_t n)
    {
        for (uint16_t_t i = 2; i < n; ++i)
            if (n % i == 0)
                return false;
        return true;
    }