AULA 5 - Microprocessadores - Graduação: mudanças entre as edições
imported>Fargoud |
imported>Fargoud |
||
| Linha 339: | Linha 339: | ||
[[image: MIPinstrpd.png|center]] | [[image: MIPinstrpd.png|center]] | ||
[[image: MIPinstrpd2.png|center]] | |||
* Extensão: são instruções para reagrupar dados; | * Extensão: são instruções para reagrupar dados; | ||
Edição das 16h41min de 3 de abril de 2018
CONJUNTOS 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
O conjunto de instruções também é chamado de "ISA - Instruction Set Architecture".
A arquitetura ARM trabalha com três 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.
O código THUMB é capaz de reduzir em até 65% o tamanho de um código ARM (maior densidade de código), e aumentar em até 160% o desempenho, em relação a um processador ARM equivalente, conectado a um sistema de 16 bits, apenas.
Características do ISA Arm
- 13 registradores de propósito geral:
- R0 a R7 (low registers)
- R8 a R12 (high registers)
- 3 registradores de uso/significado especial
- r13: stack pointer (SP)
- r14: link (LR)
- r15: (PC)
- 1 registrador de propósito especial
- xPSR = Program Status Register
- Registradores xPSR, PC, LR, R12, R3, R2, R1 e R0 são armazenados automaticamente na pilha quando uma interrupção ocorre.
- Arquitetura load-store: as instruções somente lidam com valores que estejam nos registradores (ou imediatos) e sempre armazenam resultados em algum registrador. O acesso à memória é feito apenas através das instruções load (para carregar o valor da memória em um registrador) e store (para armazenar o valor de um registrador na memória).
- Permite a execução condicional de várias instruções.
- Máquina de 3 endereços – dois registradores operandos e um registrador de resultado são especificados.
- 1º operando: especifica o local onde será armazenado o resultado da operação.
- Outros: valores imediatos ou locais onde se encontram os dados.
Exemplo: ADD R1, R2, #4 R1 ← (R2) + 4
- Uniformidade e tamanhos fixos dos campos das instruções para facilitar a decodificação.
- I/O mapeado em memória: a comunicação com dispositivos periféricos é feita no próprio espaço de endereçamento de memória (via load e store).
- Endereços de memória se referem a bytes individuais.
Formato de instruções
Apesar das diferenças entre as instruções, os projetistas mantiveram certa regularidade no formato delas, facilitando a decodificação. O formato geral é do bit menos significativo ao mais significativo, o seguinte:
- 4 bits para condição de execução;
- 3 bits para o tipo de instrução;
- 5 bits para o opcode;
- 20 bits, cujo uso e repartição varia para acomodar endereços, referências a registradores,
deslocamentos e rotações.
Operandos:
Registradores:
ADD r1, r2, r3 MOV r1, r2
Imediatos: identificados pelo símbolo #
ADD r1, r2, #10 MOV r1, #0
Maiores detalhes sobre o formato das instruções podem ser vistos na figura.
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.
Tipos de instruções
Quanto ao número de bits, as instruções podem ser dos seguintes tipos:
ARM
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.
Quanto ao tipo de operação, tipos de instruções oferecidos são:
- Load/Store: Instruções de carregamento e escrita de dados na memória. Podem ler/escrever
palavras inteiras(32 bits), meias-palavras(16 bits), bytes sem sinal, e podem ler e estender o sinal de meias-palavras.
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.
- Load/Store em blocos: são instruções que permitem definir a leitura/escrita de blocos de até 16 registradores em uma única instrução. Isso é feito com uma máscara de 16 bits na instrução
que define quais registradores serão usados, e permite atingir uma banda de comunicação quatro vezes maior do que uma instrução que usa um único registrador. Essas instruções podem ser usadas para início e retorno de chamadas, assim como para ler blocos de memória.
- Desvio: Todos os processadores ARM suportam instruções de branch com saltos condicionais para frente ou para trás de até 32M. A condição é dada por um campo de 4 bits. 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).
- Processamento de Dados: são operações de adição e subtração, operações lógicas AND, OR e
XOR, e instruções de comparação e teste;
- Multiplicação de inteiros: as multiplicações de inteiros podem ser feitas com operandos de 32 ou 16 bits, e o resultado pode ocupar 32 ou 64 bits;
- Adição/Subtração paralelas: essas instruções permitem aplicar a operação em partes dos operandos, de forma paralela.
Um exemplo é a instrução ADD16, que adiciona as primeiras meias-palavras dos operandos para formar a meia palavra superior do resultado, enquanto faz o mesmo com as meias-palavras inferiores para formar a parte inferior do resultado. Essas instruções são úteis para processamento de imagens
- Extensão: são instruções para reagrupar dados;
- Acesso a registrador de estado: é permitido ler e escrever em partes do registrador de estados. 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.
- Interface para coprocessadores: há um grupo de instruções para trocas de dados com coprocessadores, assim como para controle destes. Elas permitem ao processador ARM iniciar uma operação de processamento de um coprocessador. Permite fazer transferências entre os registradores do processador e dos coprocessadores. Permitem também o processador criar endereços para o coprocessador carregar e escrever instruções. Todo o tratamento de ponto flutuante é feito por um coprocessador, cuja presença nos chips é opcional.
- Intercâmbio com Thumb: são instruções para indicar se serão executadas instruções de 32 ou 16 bits. A instrução BX permite a troca entre o estado ARM e o estado THUMB. Esta instrução copia o conteúdo 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 geradoras de exceçõ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 códigos 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.
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).
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, 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).
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





















