AULA 5 - Microprocessadores - Graduação
CONJUNTO DE INSTRUÇÕES
Um programador de linguagem de alto nível normalmente conhece muito pouco acerca da arquitetura da máquina que está usando.
O conjunto de instruções da máquina é o limite em que o projetista de CPU e o programador de baixo nível enxergam, da mesma máquina.
Implementar uma CPU é uma tarefa que envolve, em grande parte, implementar um conjunto de instruções de máquina.
Já programar em linguagem de máquina (na verdade, em linguagem de montagem, Assembly) exige um conhecimento acerca do conjunto de registradores da CPU, a estrutura de memória, os tipos de dados disponíveis na máquina e o funcionamento da ULA.
Porém, da descrição do conjunto de instruções à uma compreensão sobre o funcionamento da CPU, há um longo caminho.
CARACTERÍSTICAS DE INSTRUÇÕES DE MÁQUINA
A operação de uma CPU é determinada pelas instruções que ela executa, conhecidas como instruções de máquina.
A coleção de instruções que uma determinada CPU é capaz de executar é conhecida como conjunto de instruções da CPU.
Elementos de instrução de máquina
Cada instrução deve conter toda a informação necessária para que a CPU possa executá-la.
A figura acima ajuda a definir os elementos da instrução:
- Código de operação (OPCODE) - Especifica a operação a ser efetuada (por exemplo, ADD, ou E/S, etc). A operação é especificada por um código binário.
- Referência a operando fonte - a operação pode envolver um ou mais operandos fonte, ou seja, que constituem dados de entrada para a operação.
Exemplos:
- operação com um único operando: INV(A)
- operação com dois operandos: ADD(A,B)
- operação com três ou mais operandos: DEV(GE(A,B),C,D)
- Referência a um operando de destino - a operação pode eventualmente produzir um resultado, que deverá ser enviado a um endereço de memória, ou registrador
- Endereço da próxima instrução - indica aonde a CPU deve buscar a próxima instrução (memória principal ou virtual), depois que a instrução corrente for completada, quando for o caso.
Os operandos fonte e destino podem estar localizados em:
- Memória principal ou virtual - implica em busca na memória RAM, ou no HD (quando virtual)
- Registrador da CPU - se houver apenas um registrador, a referência a ele poderá ser implícita. Quando há acumulador, a referência a ele é default. Caso hajam vários registradores, normalmente a referência a cada um, individualmente, é feita por um número inteiro correspondente.
- Dispositivo de E/S - indicado diretamente na instrução. Se for usada a E/S mapeada na memória, esta informação se resumirá a um endereço de memória, como os demais. Pode ser também um registrador específico (Ex: porta serial).
REPRESENTAÇÃO DE INSTRUÇÕES
Internamente, cada instrução é representada por uma sequência de bits.
É dividida em campos, correspondentes aos elementos da instrução.
Exemplos:
A maioria das CPUs têm mais de um formato de instrução.
Durante a execução, uma instrução é lida em um registrador de instruções (IR) da CPU. A CPU extrai os dados dos vários campos da instrução e efetua a operação correspondente.
Os códigos de operação normalmente são representados por abreviações, chamadas mnemônicos, que evitam que os programadores tenham que trabalhar diretamente com valores binários.
Alguns exemplos mais comuns são:
ADD ------------- Adição SUB ------------- Subtração MPY ------------- Multiplição DIV ------------- Divisão GT ------------- Operação > LT ------------- Operação < GE ------------- Operação ≥ LE ------------- Operação ≤
Os operandos também são representados por símbolos.
Por exemplo:
ADD R, Y
pode significar adicionar o valor contido na posição Y, com o valor contido no registrador R. Neste caso, Y é um endereço de memória e R, um registrador da própria CPU.
NÚMERO DE ENDEREÇOS
Uma das maneiras tradicionais de se descrever uma arquitetura é em termos do número de endereços contidos em cada instrução.
Obviamente, instruções aritméticas e lógicas requerem um número maior de operandos (e de endereços, por conseguinte). Normalmente, precisam de dois operandos que serão submetidos a um cálculo ou comparação, e de um terceiro, pra armazenar o resultado.
O endereço da próxima instrução normalmente é implícito, armazenado no registrador PC.
A figura abaixo compara o que seriam instruções típicas de um, dois ou três endereços, para computar a mesma linha de programação:
Y = (A - B) / (C + D x E)
Note que as instruções com 3 endereços implementaram o comando com apenas 4 instruções de máquina. Porém, instruções de 3 endereços não são muito comuns, porque implicam em instruções grandes.
No caso das instruções de 2 endereços, foram necessárias 6 linhas de código, duas a mais, portanto. Nestas instruções é típico que um dos endereços referencie tanto o operando, quanto o resultado. Gera opcodes menores, porém gera códigos maiores e exige maior habilidade do programador.
As instruções de 1 único endereço exigem que um segundo endereço seja sempre implícito (normalmente, o Acumulador - AC). Neste caso, foram necessárias 8 instruções para resolver o problema.
É possível também usar-se em expressões aritméticas uma instrução com ZERO endereço. Neste caso, é necessária uma organização de memória especial, chamada pilha. As instruções com zero endereço sempre referenciam os valores nas duas posições no topo da pilha.
| NÚMERO DE ENDEREÇOS | Representação simbólica | Interpretação |
|---|---|---|
| 3 | OP A, B, C | A ← B OP C |
| 2 | OP A, B | A ← A OP B |
| 1 | OP A | AC ← AC OP A |
| 0 | OP | T ← (T-1) OP T |
Além de tudo que foi citado, as instruções podem variar muito de tamanho porque o endereço em questão de cada operando pode se referir à memória física ou a um dos registradores da CPU. Como as memórias são muito maiores que o número de registradores, vão utilizar endereços muito mais longos.
Assim, existem máquinas que, quando vão endereçar um valor NA MEMÓRIA externa (RAM) utilizam mais de uma instrução para acessar o dado.
Tudo sempre vai depender da arquitetura da máquina.
TIPO DE INSTRUÇÃO
Como já vimos, uma única instrução escrita em linguagem de alto nível pode requerer várias instruções de máquina.
Os tipos de instrução de máquina que existem são:
- Processamento de dados - instruções aritméticas e lógicas
- Armazenamento/transferência de dados - instruções de memória
- Movimentação de dados - instruções de E/S
- Controle - instruções de teste e de desvio
Na maioria das vezes, as instruções são auto-explicativas.
As instruções do tipo transferência de controle, no entanto, podem ser muito complexas. Em todos os casos, exceto com elas a próxima instrução a ser executada é aquele que segue imediatamente a atual, na memória.
Nas instruções de transferência de controle, a CPU atualiza o PC com o endereço de alguma outra instrução, armazenada na memória de programa.
As instruções de transferência de controle são aquelas utilizadas quando há loops no código, ou comandos de decisão ou ainda quando o código teve que ser segmentado em vários arquivos, normalmente porque estava muito extenso.
Instruções de desvio
Nestas, um dos operandos é o endereço da próxima instrução a ser executada.
Com frequência, essa instrução é do tipo desvio condicional, isto é, o desvio só ocorrerá se uma determinada condição for satisfeita. Caso contrário, o programa segue com a próxima instrução adjacente, sem alterações no PC além do incremento normal.
Estas instruções normalmente têm o nome de BRANCH ou JUMP e podem depender de uma condição, ou ser incondicionais.
Exemplo:
Instruções de Chamada de procedimento
Um procedimento, ou função, ou ainda subrotina é um trecho de código independente que modulariza um programa.
O mecanismo de controle de procedimentos envolve duas instruções básicas: uma instrução de chamada, que desvia a instrução corrente para o início do procedimento, e uma instrução de retorno, que provoca o retorno da execução para o endereço onde ocorreu a chamada. Ambas são condições de desvio.
A figura abaixo ilustra o uso de procedimentos na execução de um programa.
INSTRUÇÕES DO PENTIUM II E DO POWERPC
Para exemplificar, apresentamos o caso especial de conjunto de instruções de um processador de propósito geral que foi muito utilizado até o início da década de (20)00.
CONJUNTO DE INSTRUÇÕES DO ARM
Chamado de "ISA - Instruction Set Architecture".
A arquitetura ARM trabalha com dois tipos de instruções: o ARM, THUMB e o THUMB-2.
O conjunto THUMB de instruções de 16 bits trabalha com registradores de 32 bits.
The THUMB set’s 16-bit instruction length allows it to approach twice the density of standard ARM code while retaining most of the ARM’s performance advantage over a traditional 16-bit processor using 16-bit registers. This is possible because THUMB code operates on the same 32-bit register set as ARM code. O código THUMB é capaz de reduzir em até 65% o tamanho de um código ARM, e aumentar em até 160% o desempenho, em relação a um processador ARM equivalente, conectado a um sistema de 16 bits, apenas.
THUMB
- Operações de 32 bits, em instruções de 16 bits
- Introduzido no processador ARM7TDMI (Datasheet)
- Suportado por todos os processadores ARM, desde então
THUMB-2
- Permite uma mistura com performance otimizada de instruções de 16/32 bits
- Todas as operações do processador podem ser realizadas no estado "Thumb"
- Suportado por todos os processadores da linha Cortex-M, desde então.
O conjunto de instruções ARM pode ser dividido em 6 classes:
- instruções de branch
- instruções de processamento de dados
- instruções de acesso ao registrador de estado
- instruções de load e store
- instruções do coprocessador
- instruções geradoras de exceções
A maioria destas instruções contêm um campo de condição de 4 bits.
Estas condições são ativadas através de sufixos que são colocados junto com os minemônicos das instruções.
Por exemplo, um salto (B) pode ser acompanhado de um NE (not equal), o que gera uma expressão BNE.
Essa instrução só será executada caso a condição seja verdadeira.
INSTRUÇÕES DE BRANCH
Todos os processadores ARM suportam instruções de branch com saltos condicionais para frente ou para trás de até 32M. Saltos mais longos podem ser feitos através de chamadas de subrotinas como por exemplo o Branch whith Link (BL) que mantem o endereço de retorno no LR (R14).
A instrução BX permite a troca entre o estado ARM e o estado THUMB. Esta instrução copia o conteudo de um registrador de propósito geral para o PC. Caso o bit[0] do valor transferido para o PC seja 1, o processador troca para o estado THUMB.
INSTRUÇÕES DE PROCESSAMENTO DE DADOS
O ARM possui 16 instruções mostrada na tabela a seguir:
INSTRUÇÕES DE ACESSO AO REGISTRADOR DE ESTADO
Existem duas instruções para mover os conteúdos de um registrador de propósito geral e de um registrador de estado, que são as seguintes: MRS – move o conteúdo do registrador de estado para um registrador de propósito geral. MSR – move o conteúdo do registrador de propósito para um registrador de estado.
INSTRUÇÕES DE LOAD E STORE
A arquitetura ARM suporta dois tipos de instruções de load e store que transferem o conteudo de um registrador para a memória ou ao contrário.
O primeiro tipo pode carregar ou escrever uma parava de 32 bits ou um byte sem sinal.
O segundo tipo pode ler ou escrever meia palavra de 16 bit sem sinal, e pode carregar e sinalizar meia palavra de 16 bit ou um byte. Este tipo de instrução está disponível apenas para a arquitetura ARM versão 4 ou posterior.
Codificação das instruções:
Instrução que lê e carrega palavra ou byte sem sinal:
I,P, U, W – são bits para distinguir diferenças entre tipos de <addressing_mode>.
L bit – Diferencia instrução de load (L==1) e store (L==0)
B bit – Diferencia byte sem sinal (B==1) e palavra(B==0)
Rn – Especifica o registrador base utilizado por <addressing_mode>
Rd – Especifica o registrador onde vai ser carregado ou de onde vai ser escrito.
Instrução que lê e carrega meia palavra ou lê um byte sinalizado :
I,P,U,W – são registradores que especificam o tipo de endereçamento
L bit - Diferencia Load (L==1) e Store (L==0)
S bit – Diferencia meia palavra sinalizada (S==1) e não sinalizada (L==0).
H bit – diferencia meia palavra (H==1) e byte sinalizado (H==0).
Rn – Especifica o registrador base a ser utilizado pelo modo de endereçamento.
Rd – Especifica o registrador do qual será lido ou onde será carregado a informação.
INSTRUÇÕES GERADORAS DE EXCESSÕES
Existe duas instruções deste tipo:
• a SWI que causa uma interrupção de software. É o mecanismo principal utilizado pelo ARM para poder executar codigos do Sistema Operacional no modo de usuário (User Mode).
• E existe o BKPT. Esta instrução é usada para breakpoints na arquitetura ARM da versão 5 em diante.
A codificação destas instruções é a seguinte:
INSTRUÇÕES DO COPROCESSADOR
São instruções para fazer a comunicação com coprecessadores. Elas permitem o processador ARM iniciar uma operação de processamento de um coprecessador. Permite fazer transferencias entre os registradores do processador e dos coprocessadores. E permite também o processador criar enderenços para o coprocessador carregar e escrever instruções.
O CONJUNTO DE INSTRUÇÕES THUMB
O THUMB possui instruções com as mesmas finalidades das instruções ARM, porém são codificadas com 16 bits. Estas instruções THUMB geram códigos menores porém muitas vezes ocorre de aumentar o número de instruções que o processador deve executar. Uma solução para isso são aplicações que utilizam ambos tipos de instruções ( ARM e THUMB). Aplicações deste tipo conseguem reduzir o tamanho do seu código significativamente e conseguem manter o consumo de energia baixo. As codificações de algumas instruções THUMB são as seguintes:
As instruções a seguir se referem às adições do conjunto de instruções "thumb2" do ARM (que alem de implementar as instruções de 16 bits no formato "thumb", acrescentou novas instruções de 32 bits ao conjunto padrão de instruções do ARM), ou, em alguns casos, não foram colocadas no resumo do Prof. Borin. Apenas as mais interessantes para a disciplina foram relacionadas. Para cada instrução colocamos o mnemônico, descrição, e o número da página correspondente no manual detalhado de instruções:
BFC - Bit field clear p-89 BFI - Bit field insert p-89 BIC - Bit Clear - p-75 CBZ/CBNZ - Compare and branch if zero/non zero - p93 CLZ - Count leading zeros - p-78 NOP - nop - p-102 ORN - Logical OR NOT - p-75 RBIT - Reverse bits - p-81 REV - Reverse byte order in a word - p-81 (passa uma palavre de "little endian" para "big endian" e vice-versa) SXTB - Sign extend a byte - p-90 TBB - Table branch byte - p-96 UDIV - Unsigned divide - p-86 UMULL - Unsigned multiply (32x32), 64 bit result UXTB - Zero extend a byte - p-90 CPSID/CPSIE - Interrupts Disable/Enable - p-98 Instruções de deslocamento As instruções a seguir são, na verdade, sinônimos para a instrução MOV Rd, Rd, XXX #n onde XXX é um dentre LSR, LSL, ASR, ROR e n é uma constante variando de 0-31. Obs: se o sufixo S for acrecentado a XXX então o último bit deslocado de Rd entra no bit C (Carry) da palavra de estado. Obs: os bits de uma palavra são numerados de 0 a 31, do menos significativo ao mais significativo LSR Rd, #n - desloca Rd n bits para a direita inserindo n 0s à esquerda (divide Rd por 2**n) LSL Rd, #n - desloca Rd n bits para a esquerda inserindo n 0s à direita (multiplica Rd por 2**n) ASR Rd, #n - desloca Rd n bits para a direita, replicando o bit 31 (preserva o bit de sinal)
- divide um inteiro com sinal por 2**n
ROR Rd, #n - rotaciona para a direita n bits de Rd (o bit 0, - significativo, entra no lugar do bit 31)
XXX Rd, Rm,#n - variante permitida das instruções acima; Rm não muda
RRX Rd, Rm - rotaciona um bit do par (Rm,C) como se fosse uma palavra de 33 bits: Rd0->C, C->Rd31, etc
- resultado vai para Rd, Rm não é alterado, exceto se Rd=Rm.
Execução condicional de instruções As instruções lógico-aritméticas podem ser condicionalmente executadas dependendo dos flags da palavra de estado que foram atualizados numa instrução anterior (usualmente imediatamente anterior). Existem 15 condições que podem ser usadas como sufixo da instrução (ver p. 58 do manual detalhado), e que são as mesmas usadas nas instruções de salto condicional: eq, ne, cs ou hs, cc ou lo, mi, pl, vs, vc, hi, ls, ge, lt, gt, le, al. Para que a execução condicional seja possível até 4 instruções devem ser precedidas pela instrução especial "If-Then condition instruction": it cond, onde cond é uma das 15 siglas de 2 letrasi citadas anteriormente (existem outros 3 possíveis parâmetros, veja p. 94 do manual detalhado de nstruções. Exemplos: (1) suponha que r1 contem um inteiro com sinal: colocar em r0 o valor absoluto desse inteiro: movs r0, r1 @ atualiza flags
it mi @ queremos tomar o negativo caso r0 <0
rsbmi r0, #0 @ faça r0 := 0 - r0 caso o bit N estivesse ligado
(2) algoritmo para calcular o máximo divisor comum de dois inteiros em r1 e r2, devolvendo o resultado em r1 e r2: loop: cmp r1, r2 @ cmp sempre atualiza flags
it gt @ queremos testar se r1 > r2
subgt r1, r2 @ se r1 > r2, faça r1:=r1-r2
it lt @ senão teste por <
sublt r2, r1 @ se r1 < r2 faça r2:= r2 -r1
bne loop @ se r1 # r2 volte ao laço
@ senão mdc=r1=r2
Estrutura de Procedures e Functions (De C para Assembler)
A chamada de funções e procedures é dividida em 6 passos:
1º Passo: Parâmetros
As funções de até 4 parâmetros tem como destino os registradores de R0-R3 , com o primeiro parâmetro no r0. Os demais são colocados na pilha e são acessados utilizando [SP+Displacement].
void func1 (
long b, //Primeiro parâmetro passado em R0 int *c, //Segundo parâmetro passado em R1 short d, //Quarto parâmetro passado em R2 float e) //Quinto parâmetro passado em R3
No caso acima, todos os argumentos cabem nos registradores.
void func2 (
long long f, //Primeiro parâmetro passado em R0/R1 int g, //Segundo parâmetro passado em R2 double h) //Terceiro parâmetro passado em R3 e Pilha long i) //Quarto parâmetro passado somente na Pilha
Já aqui, os primeiros dois são passados nos registradores, o terceiro é passado parcialmente no registrador e o quarto é passado diretamente na pilha.
2º Passo: Chamada
É chamada executando o comando de Branch and Link (Expansão e linkagem).
BL minhafunção
3º Passo: Preservação dos registradores.
A primeira instrução em uma função normalmente é assim:
stmdb sp!, {r4 - r6, lr} Ela faz um Push LR, Push R6, Push R5, Push R4.
Esta instrução preserva os valores dos registradores de R4 até R11, e o R13. É total responsabilidade da função preservar esses.
4º Passo:Variáveis Locais
Em seguida, uma operação é realizada na pilha para reservar espaço para as variáveis locais.
sub sp, sp, #0xC
5º Passo: Corpo Então, o corpo da função é executado, em algum momento o compilador decide que o registrador R0 será responsável pelo retorno da função.
6º Passo: Retorno
Se o espaço foi alocado para variáveis locais, ele é então desalocado:
add sp, sp, #0xC
Os registradores salvos no começo são restaurados:
ldmia sp!, {r4 – r6, lr}
pop(r4); pop(r5); pop(r6); pop(lr);
E finalmente o endereço de retorno:
Bx lr













