3. Utilização

3.1. Invocação

O p16as é invocado na janela de comando segundo a seguinte sintaxe:

p16as [options] <source filename>

options:
   --verbose
   -h, --help
   -v, --version
   -i, --input <source filename>
   -o, --output <base filename>
   -s, --section <section name>=<address>
   -f, --format hexintel | binary | logisim8 | logisim16
   -a, --addresses <from>-<to>
   -l, --interleave

Os erros e avisos são assinalados na janela de comando utilizada na invocação.

O ficheiro com o texto do programa em linguagem assembly – ficheiro fonte – assinalado como <source filename>, tem normalmente a extensão “s”.

A opção --output permite definir o nome base [2] dos ficheiros de saída. Se esta opção for omitida, os ficheiros produzidos terão o mesmo nome base do ficheiro fonte e serão localizados na mesma diretoria.

A opção --section permite definir o endereço de localização das secções. Em caso de omissão desta opção as secções são localizadas a partir do endereço 0x0000, pela ordem com que estão escritas no ficheiro fonte.

A opção --format permite definir o formato do ficheiro de saída com o código binário do programa. A omissão desta opção é equivalente a --format hexintel.

Formatos possíveis para o ficheiro com o código binário:

  • --format hexintel: ficheiro binário em formato Intel HEX;

  • --format binary: o conteúdo do ficheiro binário é uma imagem exata da memória;

  • --format logisim8: ficheiro binário em formato Logisim, organizado em palavras de 8 bits;

  • --format logisim16: ficheiro binário em formato Logisim, organizado em palavras de 16 bits.

A opção --addresses permite definir a gama de endereços cujo conteúdo é transcrito para o ficheiro binário de saída. O conteúdo respeitante a endereços fora do intervalo especificado é excluído do ficheiro binário de saída.

A opção --interleave faz com que o código binário do programa seja repartido por dois ficheiros – um com os bytes respeitantes aos endereços pares e outro com os bytes respeitantes aos endereços ímpares. Esta opção é ignorada se também for mencionada a opção --format logisim16.

3.2. Localização das secções

A definição da localização em memória de cada secção pode ser explícita ou implícita.

A localização explícita é definida através da opção --section na invocação do p16as.

A localização implícita aplica-se às secções omissas na localização explicita, localizando-as a seguir ao último endereço ocupado pela secção anterior, pela ordem em que estão escritas no ficheiro fonte do programa.

No caso de não ser explicitada a localização da primeira secção definida no programa, esta é localizada no endereço 0x0000.

No caso da secção estar fragmentada, e aplicando-se a localização implícita, a sua localização é definida pela posição do primeiro fragmento.

O início de uma secção é localizado num endereço par.

3.3. Formatos de saída

O código binário do programa é guardado em ficheiro num de três formatos: formato Intel HEX [1], formato binário e formato do simulador Logisim.

O conteúdo das secções .stack e .bss não é transposto para os ficheiros de saída.

3.3.1. Formato binário

No formato binário o conteúdo do ficheiro produzido é a imagem exata do conteúdo da memória do sistema. O primeiro byte do ficheiro corresponde ao endereço da primeira secção. Ao que se segue o restante conteúdo da primeira secção e das secções seguintes. Se existirem intervalos de espaço de endereçamento entre secções, serão preenchidos com o valor zero.

3.3.2. Formato Logisim

O simulador Logisim simula dispositivos de memória RAM ou ROM, cujo conteúdo pode ser carregado a partir de ficheiro.

Na utilização do Logisim na simulação de sistemas baseados no P16 é necessário carregar nesses dispositivos o código binário dos programas, produzido pelo p16as.

No Logisim os dispositivos de memória podem ter palavras com qualquer número de bits. Em ficheiro, o conteúdo da memória é guardado em formato de texto, como uma sequência de valores numéricos representados em base hexadecimal, em que cada valor corresponde a uma posição de memória.

As ocorrências de sucessivos valores iguais são representadas pela sequência N*X. Sendo N o número de vezes que o valor ocorre e X o valor em si.

O p16as gera ficheiros binários em formato Logisim para memórias com palavras de 8 ou 16 bits.

3.4. Exemplo de utilização

Considere-se o programa da Listagem 3.1 como conteúdo do ficheiro multiply.s.

Código fonte: multiply.s

Listagem 3.1 Ficheiro multiply.s
 1	.section .startup
 2	b	_start
 3	b	.
 4_start:
 5	ldr	sp, addressof_stack_top
 6	mov	r0, pc
 7	add	lr, r0, #4
 8	ldr	pc, addressof_main
 9	b	.
10
11addressof_stack_top:
12	.word	stack_top
13addressof_main:
14	.word	main
15
16	.text
17	.data
18
19	.stack
20stack:
21	.space	1024
22stack_top:
23
24/*---------------------------------------------------
25uint8_t m = 20, n = 3;
26*/
27	.data
28m:
29	.byte	20
30n:
31	.byte	3
32
33/*---------------------------------------------------
34uint16_t p, q;
35*/
36
37p:
38	.word	0
39q:
40	.word	0
41
42/*---------------------------------------------------
43int main() {
44	p = multiply(m, n);
45	q = multiply(4, 7);
46}
47*/
48	.text
49main:
50	push	lr
51	ldr	r0, addressof_m
52	ldrb	r0, [r0]
53	ldr	r1, addressof_n
54	ldrb	r1, [r1]
55	bl	multiply
56	ldr	r1, addressof_p
57	str	r0, [r1]
58	mov	r0, #4
59	mov	r1, #7
60	bl	multiply
61	ldr	r1, addressof_q
62	str	r0, [r1]
63	pop	pc
64
65addressof_m:
66	.word	m
67addressof_n:
68	.word	n
69addressof_p:
70	.word	p
71addressof_q:
72	.word	q
73
74/*---------------------------------------------------
75<r0> uint16_t multiply(<r0> uint8_t multiplicand, <r1> uint8_t multiplier) {
76	<r2> uint16_t product = 0;
77	while (multiplier > 0) {
78		product += multiplicand;
79		multiplier--;
80	}
81	<r0> return product;
82}
83*/
84multiply:
85	mov	r2, #0
86	add	r1, r1, #0
87	bzs	while_end
88while:
89	add	r2, r2, r0
90	sub	r1, r1, #1
91	bzc	while
92while_end:
93	mov	r0, r2
94	mov	pc, lr

Invocação

No comando

p16as multiply.s -s .data=0x4000 -s .text=0x1000

a primeira ocorrência da opção -s define o endereços da secção .data em 0x4000 e a segunda ocorrência define o endereço da secção .text em 0x1000. A omissão de outras opções define que: os ficheiros de saída terão os mesmos nomes base – multiply.*; o ficheiro com o código binário terá o formato HEX Intel; o conteúdo binário não é filtrado por endereço; não serão gerados dois ficheiros binários com os dados intercalados.

Mensagens de erro e de aviso

Foram introduzidas modificações no ficheiro fonte para exemplificar e emissão de mensagens.

O primeiro caso é um erro de sintaxe – a definição duma mnemónica de instrução inexistente (ld).

multiply.s (51):     ld      r0, addressof_m
----------------     ^^
ERROR!       syntax error

O segundo caso é um erro de domínio – é escrito o número 17 na posição de uma constante cujo domínio vai de 0 a 15.

multiply.s (90):     sub     r1, r1, 17
----------------                     ^^
WARNING!     Expression's value = 17 (0x11) not encodable in 4 bit, truncate to 1 (0x1)

Se forem assinaladas apenas mensagens de aviso o processamento prossegue com a geração dos ficheiros de saída – multiply.lst e multiply.hex. Note-se que faz parte das boas práticas de programação corrigir o programa até suprimir a emissão de mensagens de aviso.

Organização

Por uma questão de organização, é conveniente criar especificamente uma diretoria para alojar os ficheiros relacionados com um dado programa. No exemplo seguinte a diretoria multiply aloja todos os ficheiros relacionados com este programa: multiply.s, multiply.lst e multiply.hex.

disciplinas
   |-- pe
   |-- ss
   |-- ac
      |-- documents
      |-- p16_code
         |-- divide
         |-- multiply
            |-- multiply.s
            |-- multiply.lst
            |-- multiply.hex

Ficheiro lst

O ficheiro de extensão lst contém a tabela de secções, a tabela de símbolos e a listagem das instruções.

Na tabela de secções listam-se as secções existentes, as gamas de endereços que ocupam e as respetivas dimensões. A secção .startup é localizada no endereço 0x0000, por localização implícita, porque está definida em primeiro lugar no ficheiro fonte. As secções .text e .data são localizadas, respetivamente, nos endereços 0x1000 e 0x4000 por localização explícita. A secção .stack é localizada no endereço 0x4006, por localização implícita, porque está definida a seguir a .data que tem a dimensão 6.

Na tabela de símbolos listam-se os símbolos definidos através de label ou através da diretiva .equ. Por cada símbolo é dada a seguinte informação: identificador, tipo, valor associado e secção a que pertence.

Na listagem das instruções, são apresentados do lado esquerdo, na primeira coluna o número da linha do ficheiro fonte, na segunda coluna os endereços da memória e na terceira e quarta colunas o respetivo conteúdo.

Na arquitetura do P16 as palavras formadas por dois bytes – designadas por word – ocupam duas posições de memória consecutivas, o byte de menor peso toma a posição de endereço menor e o byte de maior peso, a posição de endereço maior – little ended format.

O conteúdo da memória – código das instruções ou valor das variáveis – é escrito na terceira e quarta colunas como uma sequência de bytes pela ordem dos endereços que ocupam na memória. Por exemplo, na linha 7, o código máquina da instrução add    lr, r0, 4, que ocupa os endereços 0008 e 0009, e tem o valor 0xA20E, é representado pela sequência de bytes 0E A2. Por exemplo, na linha 29, a variável m, do tipo .byte, ocupa o endereço 0x0046 e o seu valor é 20 (0x14).

Listagem 3.2 Ficheiro multiply.lst
P16 assembler v1.4.99 (Jan 10 2024)	multiply.lst	Wed Jan 10 14:22:21 2024

Sections
Index   Name            Address   Size
0       .startup        0000      0012 18
1       .text           1000      0034 52
2       .data           4000      0006 6
3       .stack          4006      0400 1024

Symbols
Name                    Type      Value       Section
_start                  LABEL     0004 4      .startup
addressof_m             LABEL     101C 4124   .text
addressof_main          LABEL     0010 16     .startup
addressof_n             LABEL     101E 4126   .text
addressof_p             LABEL     1020 4128   .text
addressof_q             LABEL     1022 4130   .text
addressof_stack_top     LABEL     000E 14     .startup
line#3                  LABEL     0002 2      .startup
line#9                  LABEL     000C 12     .startup
m                       LABEL     4000 16384  .data
main                    LABEL     1000 4096   .text
multiply                LABEL     1024 4132   .text
n                       LABEL     4001 16385  .data
p                       LABEL     4002 16386  .data
q                       LABEL     4004 16388  .data
stack                   LABEL     4006 16390  .stack
stack_top               LABEL     4406 17414  .stack
while                   LABEL     102A 4138   .text
while_end               LABEL     1030 4144   .text

Code listing
   1           		.section .startup
   2 0000 01 58		b	_start
   3 0002 FF 5B		b	.
   4           	_start:
   5 0004 4D 0C		ldr	sp, addressof_stack_top
   6 0006 80 B7		mov	r0, pc
   7 0008 0E A2		add	lr, r0, #4
   8 000A 2F 0C		ldr	pc, addressof_main
   9 000C FF 5B		b	.
  10           	
  11           	addressof_stack_top:
  12 000E 06 44		.word	stack_top
  13           	addressof_main:
  14 0010 00 10		.word	main
  15           	
  16           		.text
  17           		.data
  18           	
  19           		.stack
  20           	stack:
  21 4006 00   		.space	1024
  21 .... ..
  21 4405 00
  22           	stack_top:
  23           	
  24           	/*---------------------------------------------------
  25           	uint8_t m = 20, n = 3;
  26           	*/
  27           		.data
  28           	m:
  29 4000 14		.byte	20
  30           	n:
  31 4001 03		.byte	3
  32           	
  33           	/*---------------------------------------------------
  34           	uint16_t p, q;
  35           	*/
  36           	
  37           	p:
  38 4002 00 00		.word	0
  39           	q:
  40 4004 00 00		.word	0
  41           	
  42           	/*---------------------------------------------------
  43           	int main() {
  44           		p = multiply(m, n);
  45           		q = multiply(4, 7);
  46           	}
  47           	*/
  48           		.text
  49           	main:
  50 1000 0E 24		push	lr
  51 1002 C0 0C		ldr	r0, addressof_m
  52 1004 00 08		ldrb	r0, [r0]
  53 1006 B1 0C		ldr	r1, addressof_n
  54 1008 11 08		ldrb	r1, [r1]
  55 100A 0C 5C		bl	multiply
  56 100C 91 0C		ldr	r1, addressof_p
  57 100E 10 20		str	r0, [r1]
  58 1010 40 60		mov	r0, #4
  59 1012 71 60		mov	r1, #7
  60 1014 07 5C		bl	multiply
  61 1016 51 0C		ldr	r1, addressof_q
  62 1018 10 20		str	r0, [r1]
  63 101A 0F 04		pop	pc
  64           	
  65           	addressof_m:
  66 101C 00 40		.word	m
  67           	addressof_n:
  68 101E 01 40		.word	n
  69           	addressof_p:
  70 1020 02 40		.word	p
  71           	addressof_q:
  72 1022 04 40		.word	q
  73           	
  74           	/*---------------------------------------------------
  75           	<r0> uint16_t multiply(<r0> uint8_t multiplicand, <r1> uint8_t multiplier) {
  76           		<r2> uint16_t product = 0;
  77           		while (multiplier > 0) {
  78           			product += multiplicand;
  79           			multiplier--;
  80           		}
  81           		<r0> return product;
  82           	}
  83           	*/
  84           	multiply:
  85 1024 02 60		mov	r2, #0
  86 1026 11 A0		add	r1, r1, #0
  87 1028 03 40		bzs	while_end
  88           	while:
  89 102A 22 80		add	r2, r2, r0
  90 102C 91 A8		sub	r1, r1, #1
  91 102E FD 47		bzc	while
  92           	while_end:
  93 1030 00 B1		mov	r0, r2
  94 1032 0F B7		mov	pc, lr
  94           	

Ficheiro hex

O ficheiro de extensão hex contém a informação binária do programa em formato Intel HEX. A informação é composta pelo código binário das instruções ou os valores iniciais das variáveis e a indicação dos endereços de memória onde serão carregados.

Listagem 3.3 Ficheiro multiply.hex
:100000000158FF5B4D0C80B70EA22F0CFF5B06441E
:020010000010DE
:101000000E24C00C0008B10C11080C5C910C1020CF
:1010100040607160075C510C10200F0400400140DB
:1010200002400440026011A00340228091A8FD47C5
:0410300000B10FB745
:06400000140300000000A3
:00000001FF

O seu conteúdo é composto por tramas, formadas por uma marca inicial, a dimensão dos dados, o endereço onde os dados serão carregados, o tipo da trama, os dados contidos na trama e um código para deteção de eventual corrupção dos dados – soma de controlo.

_images/hexintel.png

Figura 3.1 Formato de uma trama Intel Hex.

A soma de controlo é calculada de modo que a adição, em módulo 0x100, de todos os bytes que formam a trama, some zero.

A trama :00 0000 01 FF tem dimensão zero, invoca virtualmente o endereço zero e é do tipo 01end of file, o que conjuntamente suscita a soma de controlo FF. Serve para terminar o ficheiro.

Footnotes