2. Linguagem assembly

Um programa em linguagem assembly é formado por uma sequência de linhas de texto. Cada linha contém uma instrução, diretiva ou label.

Uma instrução é formada por uma mnemónica, que identifica a instrução, seguida dos parâmetros. No exemplo, sub é a mnemónica da instrução e r0, r0, 1 são os parâmetros.

sub        r0, r0, #1

O P16 dispõe das instruções listadas na tabela (Tabela 2.1) [2]. A quantidade e o tipo dos parâmetros dependem da instrução em causa e podem ser registos do processador, constantes numéricas ou símbolos.

As mnemónicas das instruções e o nome dos registos podem ser escritos em letras maiúsculas ou minúsculas (case insensitive [1]).

Tabela 2.1 Instruções do P16

ldr   rd, label

add     rd, rn, rm

and  rd, rn, rm

pop   rd

sub     rd, rn, rm

orr  rd, rn, rm

push  rs

adc     rd, rn, rm

eor  rd, rn, rm

ldr   rd, [rn, #constant]

sbc     rd, rn, rm

mvn  rd, rs

ldrb  rd, [rn, #constant]

add     rd, rn, #constant

lsl  rd, rn, #constant

ldr   rd, [rn, rm]

sub     rd, rn, #constant

lsr  rd, rn, #constant

ldrb  rd, [rn, rm]

cmp     rn, rm

asr  rd, rn, #constant

str   rs, [rn, #constant]

bzs/beq label

ror  rd, rn, #constant

strb  rs, [rn, #constant]

bzc/bne label

rrx  rd, rn

str   rs, [rn, rm]

bcs/blo label

mov  rd, rs

strb  rs, [rn, rm]

bcc/bhs label

movs pc, lr

msr   cpsr, rs

bge     label

mov  rd, #constant

msr   spsr, rs

blt     label

movt rd, #constant

mrs   rd, cpsr

b       label

mrs   rd, spsr

bl      label

Label

Se for necessário referenciar uma instrução ou variável, precede-se essa instrução ou variável de uma label.

cycle:
        sub  r0, r0, #1
        . . .
        b    cycle

No exemplo acima, a label cycle: precede a instrução sub  r0, r0, #1 para indicar o local para onde a instrução b  cycle deve «saltar» ao reiniciar um novo ciclo.

Comentários

Os comentários podem ser inseridos em qualquer lugar quando delimitados pelas marcas '/*' e '*/' como em linguagem C, ou depois do carácter ';' até ao fim da linha.

/* Main cycle.
Iterates until counter reach zero.
*/
cycle:
        sub  r0, r0, 1       ; decrement counter
        . . .
        b    cycle

2.1. Diretivas

Diretivas de compilação são comandos que permitem controlar a operação do assembler.

Sintaticamente uma diretiva é identificada por uma palavra chave iniciada pelo carácter '.'. No texto do programa, uma diretiva e os seus parâmetros ocupam a mesma posição da mnemónica da instrução e dos respetivos parâmetros.

Na linguagem assembly do P16 existem diretivas para definir dados do programa, para controlar a localização dos dados e do código máquina em memória e para definir símbolos.

Diretivas para definição de dados do programa

Tabela 2.2 Diretivas para definição de dados do programa

.byte [expr1 [, expr2, …]]

Reserva uma sequência de bytes, cada um inicializado com o valor da respetiva expressão. Sem argumento não produz efeito.

.word [expr1 [, expr2, …]]

Reserva uma sequência de words, cada uma inicializada com o valor da respetiva expressão. Sem argumento não produz efeito. Uma word é composta por dois bytes, sendo o de menor peso colocado na posição de menor endereço que deve ser um endereço par.

.space size [, fill ]

Reserva um bloco de memória com a dimensão em bytes indicada pelo parâmetro size, sendo cada byte inicializado com o valor da expressão fill. Se o argumento fill for omitido o bloco será preenchido com zeros.

.ascii "string1" [, "string2", …]

Reserva uma porção de memória para alojar a sequência de strings definida. As strings produzidas são compostas apenas pelos caracteres indicados, não contêm terminador.

.asciz "string1" [, "string2", …]

Reserva uma porção de memória para alojar a sequência de strings definida. As strings produzidas são compostas pelos caracteres indicados e terminadas com o valor zero (como na linguagem C).

.align [n]

Avança o contador de localização até um valor múltiplo de 2^n. O novo valor do contador terá zero nos n bits de menor peso. A omissão de argumento é equivalente a .align 1.

Exemplo de utilização da diretiva .word na definição de uma variável de 16 bits inicializada com o valor mil. Este valor é representado a 16 bits (0000 0011 1110 1000), sendo o byte de menor peso guardado na posição de memória de endereço menor e o byte de maior peso guardado na posição de endereço maior.

counter:
     .word   1000

Exemplo de utilização da diretiva .byte na definição de um array de três posições iniciadas com 3, 4 e 5, sucessivamente. São ocupadas três posições de memória. A posição de endereço mais baixo recebe o valor 3, a seguinte o valor 4 e a última o valor 5.

array:
     .byte   3, 4, 5

Exemplo de utilização da diretiva .asciz para definição de um array de caracteres iniciado com a string "Portugal", no formato da linguagem C. Neste formato cada posição do array guarda o código de um carácter, começando no endereço mais baixo e pela ordem de escrita. A terminação da string é assinalada com o valor zero na posição a seguir à do último carácter. Neste exemplo são ocupadas nove posições de memória, oito para os códigos dos caracteres e uma para o terminador.

message:
     .asciz  "Portugal"

Diretivas para definição de secções

Tabela 2.3 Diretivas para definição de secções

.section name

Define uma secção, designada por name.

.text

Define uma secção com o nome .text.

.rodata

Define uma secção com o nome .rodata.

.data

Define uma secção com o nome .data.

.bss

Define uma secção com o nome .bss.

.stack

Define uma secção com o nome .stack.

A inserção de uma diretiva de definição de secção no texto do programa, significa que todos os elementos de programa, instruções ou variáveis, definidos depois dessa diretiva, são alojados nessa secção.

Diretivas para definição de símbolos

Tabela 2.4 Diretiva para definição de símbolos

.equ name, value

Define o símbolo name como sendo equivalente a value.

A diretiva .equ permite definir um símbolo e associar-lhe um valor. Esse valor pode ser expresso na forma de um número, outro símbolo também definido com .equ, uma label ou uma expressão envolvendo qualquer um dos anteriores. O valor deve ser calculável pelo assembler, isto é, não pode envolver símbolos que não estejam definidos.

Esta diretiva pode ser posicionada em qualquer lugar do texto do programa, tanto antes como depois da invocação do respetivo símbolo.

2.2. Símbolos

Um símbolo é uma palavra iniciada por uma letra seguida de mais letras ou dígitos. Pode conter o carácter '_', tanto no início da palavra, como entre caracteres. A formação de símbolos é case sensitive [1].

Os símbolos podem ser definidos através de labels ou da diretiva .equ.

label

Uma label é formada pelo símbolo seguido do carácter ':'. Na invocação de uma label utiliza-se apenas o símbolo sem o carácter ':'. Um símbolo do tipo label é equivalente ao endereço de memória onde o código binário da instrução ou o valor da variável que lhe sucede está alojado.

No exemplo seguinte, a label counter: define o símbolo counter, cujo valor associado é o endereço de memória onde está alojado o conteúdo da variável.

counter:
     .word   0

Para melhor evidência, a label costuma colocar-se na linha anterior à da instrução ou variável a que se refere.

.equ

No exemplo seguinte, a diretiva .equ define o símbolo MODE_MASK equivalente ao valor binário 1110.

.equ    MODE_MASK, 0b00001110
Em geral, na composição léxica de símbolos usam-se as seguintes convenções:
  • na label, letras minúsculas e o carácter '_' na separação de palavras em símbolos compostos.

  • na diretiva .equ, letras maiúsculas e o carácter '_' na separação de palavras em símbolos compostos.

2.3. Expressões

Os parâmetros constantes das instruções ou diretivas são definidos através de expressões. Estas podem ser valores numéricos simples ou expressões envolvendo vários operadores.

O valor de uma expressão ou sub-expressão é um número natural (conjunto N) no domínio \(0\) a \(2^{16} - 1\).

Exemplos de expressões:

mov    r0, #3
movt   r0, #((1 << 13) >> 8)

.word  1000 / 2 + 56

As expressões podem conter símbolos definidos com a diretiva .equ com base noutra expressão.

Exemplo de expressão com símbolos:

.equ   MASK, 1 << 4
.equ   VALUE, 2055

mov    r0, #VALUE & MASK

No cálculo das expressões, o resultado de cada operação é avaliado. Se a sua magnitude não for inferior a \(2^{16}\) (não codificável a 16 bits), é emitida uma mensagem de aviso.

No caso da subtração ou da operação unária negativo, um suposto resultado negativo com magnitude inferior a \(2^{16}\) é tratado como um resultado correto.

Por exemplo a expressão -1 é codificada da mesma forma que a expressão 0xffff.

No seguinte exemplo, ambas as instruções carregam o valor 0xff em R0.

mov  r0, #-1 >> 8
mov  r0, #0xffff >> 8

2.3.1. Operadores aritméticos

2.3.2. Operadores relacionais

Os operadores relacionais produzem valores booleanos. O valor booleano verdadeiro é representado como o valor inteiro 1, e o valor booleano falso é representado como o valor inteiro 0.

Na seguinte instrução, o registo destino (R0) é afetado com o valor 1.

mov    r0, #3 == (5 - 2)

2.3.3. Operadores lógicos

Nestas operações os operandos são valores numéricos encarados como valores booleanos. Um valor numérico zero é encarado como falso e um valor numérico diferente de zero é encarado como verdadeiro.

Na seguinte instrução, o registo destino (R0) é afetado com o valor 1. Ambos os valores, 4 e 2, são encarados como valores booleanos verdadeiro, por serem diferentes de zero:

mov    r0, #4 && 2

2.3.4. Operadores bit-a-bit

Nestas operações, cada bit de um operando é operado com o bit da mesma posição do outro operando.

Na seguinte instrução, o registo destino (R0) é afetado com o valor 6.

mov    r0, #7 & 0b1110

2.3.5. Operadores deslocamento

Tabela 2.5 Operadores deslocamento

<<

deslocar para a esquerda

>>

deslocar para a direita

A operação << insere zero na posição de menor peso do resultado.

Como os valores são encarados como números naturais, a operação >> insere sempre zero na posição de maior peso do resultado.

2.3.6. Operador condicional

Este operador tem o mesmo significado que na linguagem C.

condition_expression ? true_expression : false_expression

O valor produzido por este operador é igual ao da expressão true_expression, se o valor de condition_expression for avaliado como verdadeiro ou é igual ao da expressão false_expression se o valor de condition_expression for avaliado como falso.

2.4. Contador de localização

Existe um contador de localização, associado a cada secção, que é inicializado a zero.

À medida que as instruções ou diretivas são processadas, o contador de localização é aumentado da dimensão de memória necessária para armazenar o código máquina da instrução ou o conteúdo da variável.

A linguagem assembly do P16 usa o símbolo '.' (um ponto isolado) como identificador do contador de localização. No contexto da linguagem assembly, este símbolo substitui a label referente à instrução ou diretiva corrente.

b       .

No exemplo, a instrução branch realiza um salto para a posição onde ela própria se encontra, confinando o processamento à execução repetida desta instrução.

2.5. Secções

As secções são zonas de memória que alojam elementos do programa – o código de instruções ou os dados do programa, segundo critérios de afinidade. O caso mais simples consiste em agrupar o código das instruções numa secção e as variáveis noutra secção.

Antes de especificar qualquer instrução ou diretiva deve-se definir a secção que as vai conter. A secção corrente é definida pela diretiva .section ou pelas diretivas especificas .text. .rodata, .data, .bss ou .stack.

O programa da Listagem 2.1 é composto pela secção .data (linha 1) onde se alojam as variáveis x, y e z, especificadas pela diretivas que se seguem, e pela secção .text (linha 9) onde se aloja o código máquina das instruções que se seguem. A secção .data está localizada no endereço 0x1000 e tem dimensão de seis bytes. A secção .text está localizada no endereço 0x4000 e tem a dimensão de 22 (0x16) bytes. Os valores dos endereços usados neste exemplo são arbitrários. Conforme se pode ver na Secção 3.2, os endereços das secções são atribuídos em fase posterior à da escrita do programa.

Listagem 2.1 Exemplo de utilização das secções .text, .data e .bss
P16 assembler v1.4.99 (Jan 10 2024)	sections/seccoes1.lst	Wed Jan 10 14:22:21 2024

Sections
Index   Name            Address   Size
0       .data           0000      0006 6
1       .text           0006      0016 22

Symbols
Name                    Type      Value       Section
addressof_x             LABEL     0016 22     .text
addressof_y             LABEL     0018 24     .text
addressof_z             LABEL     001A 26     .text
main                    LABEL     0006 6      .text
x                       LABEL     0000 0      .data
y                       LABEL     0002 2      .data
z                       LABEL     0004 4      .data

Code listing
   1           		.data
   2           	x:
   3 0000 1E 00		.word	30
   4           	y:
   5 0002 04 00		.word	4
   6           	z:
   7 0004 00 00		.word	0
   8           	
   9           		.text
  10           	main:
  11 0006 70 0C		ldr	r0, addressof_x
  12 0008 00 00		ldr	r0, [r0]
  13 000A 61 0C		ldr	r1, addressof_y
  14 000C 11 00		ldr	r1, [r1]
  15 000E 80 80		add	r0, r0, r1
  16 0010 41 0C		ldr	r1, addressof_z
  17 0012 10 20		str	r0, [r1]
  18 0014 0F B7		mov	pc, lr
  19           	
  20           	addressof_x:
  21 0016 00 00		.word	x
  22           	addressof_y:
  23 0018 02 00		.word	y
  24           	addressof_z:
  25 001A 04 00		.word	z
  25           	

Fragmentação

Uma secção pode ser fragmentada ao longo do texto do programa. Por exemplo, para que as variáveis possam ser definidas junto ao código das funções que as utilizam, mas alojadas na secção .data, haverá fragmentos de .text entrecortados por fragmentos de .data. Durante a compilação, os fragmentos de uma secção são concatenados, pela ordem em que aparecem ao longo da descrição do programa para formar a composição final de cada uma das secções.

Listagem 2.2 Exemplo de secções entrecortadas
P16 assembler v1.4.99 (Jan 10 2024)	sections/seccoes2.lst	Wed Jan 10 14:40:42 2024

Sections
Index   Name            Address   Size
0       .data           1000      0003 3
1       .text           3000      0010 16

Symbols
Name                    Type      Value       Section
accumulate              LABEL     3008 12296  .text
addr_counter            LABEL     300E 12302  .text
addr_ptr                LABEL     3006 12294  .text
counter                 LABEL     1002 4098   .data
ptr                     LABEL     1000 4096   .data
strtok                  LABEL     3000 12288  .text

Code listing
   1           	/*--------------------------------------------
   2           		Function strtok
   3           	*/
   4           		.data
   5           	ptr:
   6 1000 00 00		.word	0
   7           	
   8           		.text
   9           	strtok:
  10 3000 20 0C		ldr	r0, addr_ptr
  11 3002 00 00		ldr	r0, [r0]
  12           		; ...
  13 3004 0F B7		mov	pc, lr
  14           	
  15           	addr_ptr:
  16 3006 00 10		.word	ptr
  17           	
  18           	/*--------------------------------------------
  19           		Function accumulate;
  20           	*/
  21           		.data
  22           	counter:
  23 1002 00		.byte	0
  24           		
  25           		.text
  26           	accumulate:
  27 3008 20 0C		ldr	r0, addr_counter
  28 300A 00 08		ldrb	r0, [r0]
  29           		; ...
  30 300C 0F B7		mov	pc, lr
  31           	
  32           	addr_counter:
  33 300E 02 10		.word	counter
  33           	

Neste exemplo, mostram-se os endereços calculados de forma coerente, embora tendo por base valores novamente arbitrários. A variável ptr é alojada na secção .data e a sua definição surge junto da função strtok que a utiliza. O mesmo acontece com a variável counter e a função accumulate. A secção text está separada em dois fragmentos o primeiro contém o código da função strtok e o segundo contém o código da função accumulate. Na memória as duas variáveis ocupam posições contíguas – ptr ocupa as posições de endereço 0x1000 e 0x1001 e counter a posição de endereço 0x1002. O código das funções strtok e accumulate ocupam também zonas de memória contíguas, respetivamente, a gama de endereços 0x3000 a 0x3007 e a gama de endereços 0x3008 a 0x300f.

2.6. Regras sintáticas

A linguagem assembly do P16 é semelhante à usada pelo assembler AS da GNU quando usado no desenvolvimento de programas para a arquitetura ARM. O objetivo é facilitar ao estudante a transição para essa arquitetura.

Tabela 2.6 Elementos da notação Wirth Syntax Notation (WSN)

[a]

O elemento a é opcional.

a | b

a ou b são elementos alternativos

{a}

O elemento a pode não existir ou repetir-se indefinidamente

"a"

Elemento terminal

Descrevem-se na Listagem 2.3, em notação Wirth Syntax Notation (WSN) Tabela 2.6 [2], as regras sintáticas a aplicar na escrita de programas em linguagem assembly do P16.

Listagem 2.3 Regras sintáticas da linguagem assembly
program = statement { statement }.

statement =
  [label] [instruction | directive] "EOL" .

directive =
  ( ".section" identifier )
  | ".text"
  | ".rodata"
  | ".data"
  | ".bss"
  | ".stack"
  | ".align" [ expression ]
  | ".equ" identifier "," expression )
  | ( ".byte" | ".word" ) expression { "," expression }
  | ".space" expression [ "," expression ] )
  | ( ".ascii" | ".asciz" ) string { "," string } .

instruction =
  "ldr" reg0-15 "," ( "[" ("pc" | "r15") "," expression "]" ) | identifier

  | ( ( "ldr" | "str" ) ["b"] reg0-15 ","
     ( "[" reg0-7 ["," (reg0-15 | #expression)] "]" )
  | ( "mov" | "movt" ) reg0-15, (reg0-15 | #expression)
  | ( "push" | "pop" ) ["{"] reg0-15
  | ( "add" | "sub" ) reg0-15, reg0-7, (reg0-15 | #expression)
  | ( "adc" | "sbc" ) reg0-15, reg0-7,  reg0-15
  | "cmp" reg0-7, reg0-15
  | ( "and" | "orr" | "eor" ) reg0-15, reg0-7, reg0-15
  | ( "mvn" | "not") reg0-15, reg0-15
  | ( "lsl" | "lsr" | "asr" | "ror" )        reg0-15, reg0-7, #expression
  | "rrx" reg0-15, reg0-7
  | "msr" psw "," reg0-15
  | "mrs" reg0-15 "," psw
  | ( "bzs" | "beq" | "bzc" | "bne" | "bcs" | "blo" | "bcc" | "bhs"
  | "blt" | "bge" | "bl" | "b" ) identifier
  | "movs pc, lr" .

reg0-7 = "r0" | "r1" | "r2" | "r3" | "r4" | "r5" | "r6" | "r7" .
   | "R0" | "R1" | "R2" | "R3" | "R4" | "R5" | "R6" | "R7" .

reg0-15 = reg0-7
  | "r8" | "r9" | "r10" | "r11" | "r12" | "r13" | "r14" | "r15"
  | "R8" | "R9" | "R10" | "R11" | "R12" | "R13" | "R14" | "R15"
  | "sp" | "lr" | "pc" | "SP" | "LR" | "PC" .

psw = "cpsr" | "spsr" | "CPSR" | "SPSR".

expression = logical_or_expression
  | logical_or_expression "?" expression ":" expression .

logical_or_expression = logical_and_expression
  | logical_or_expression "||" logical_and_expression .

logical_and_expression = inclusive_or_expression
  | logical_and_expression "&&" inclusive_or_expression .

inclusive_or_expression = exclusive_or_expression
  | inclusive_or_expression "|" exclusive_or_expression .

exclusive_or_expression = and_expression
  | exclusive_or_expression "^" and_expression .

and_expression = equality_expression
  | and_expression "&" equality_expression .

equality_expression = relational_expression
  | equality_expression "==" relational_expression
  | equality_expression "!=" relational_expression .

relational_expression = shift_expression
  | relational_expression "<" shift_expression
  | relational_expression ">" shift_expression
  | relational_expression "<=" shift_expression
  | relational_expression ">=" shift_expression .

shift_expression = additive_expression
  | shift_expression "<<" additive_expression
  | shift_expression ">>" additive_expression .

additive_expression = multiplicative_expression
  | additive_expression "+" multiplicative_expression
  | additive_expression "-" multiplicative_expression .

multiplicative_expression = unary_expression
  | multiplicative_expression "*" unary_expression
  | multiplicative_expression "/" unary_expression
  | multiplicative_expression "%" unary_expression .

unary_expression = primary_expression
  | "+" primary_expression
  | "-" primary_expression
  | "!" primary_expression
  | "~" primary_expression .

primary_expression = literal | identifier | "(" expression ")" .

identifier = ("." | alphabet | "_") { "." | alphabet | number | "_" }.

label =  identifier ":" .

literal = decimal | hexadecimal | octal | binary | "'" character "'" .

decimal = "0" | (("1" | ... | "9") { decimal_digit } ) .

hexadecimal = "0" ("x" | "X") hexadecimal_digit { hexadecimal_digit } .

octal = "0" ("1" | ... | "7") { octal_digit } .

binary = "0" ("b" | "B") ("0" | "1") { "0" | "1" } .

octal_digit = "0" | "1" | ... | "6" | "7" .

decimal_digit = "0" | "1" | ... | "8" | "9" .

hexadecimal_digit = decimal_digit | "a" | ... | "f" | "A" | ... | "F" .

alphabet = "a" | ... | "z" | "A" | ... | "Z" .

character = alphabet | decimal_digit
  | "[" | "]" | "{" | "}" | "(" | ")" | "<" | ">"
  | "=" | "|" | "&" | "%" | "$" | "#" | "/" | "?" | "!" | "_" | "*"
  | "\b" | "\t" | "\n" | "\f" | "\r" | "\\" | "\"" | "\'"
  | ( "\" ( decimal | hexadecimal | octal | binary ) ) .

string = "\"" character { character } "\"" .

"EOL" = control character for end of line

2.7. Limitações sintáticas

  • Não é possível definir símbolos iguais a mnemónicas de instruções. Por exemplo, não pode existir um símbolo "b" porque coincide com a mnemónica da instrução branch.

  • Se ocorrer omissão do último elemento sintático, a visualização do erro é assinalada corretamente, mas é dada indicação da linha seguinte.

Footnotes