Você está na página 1de 75

Capítulo 8

Arquitetura de
processadores
Mostraremos neste capítulo alguns conceitos importantes sobre o
funcionamento interno dos processadores. Tomaremos como exemplo os
processadores Intel, e com eles você entenderá conceitos como execução
especulativa, pipeline, previsão de desvio, paralelismo, micro-operações,
linguagem assembly, memória virtual, paginação e outros termos complexos.
O assunto é difícil, mas vale a pena, pois o leitor passará a ter um
conhecimento mais profundo sobre o que se passa dentro de um
processador.

Registradores internos do processador


Para entender como um processador executa programas, precisamos
conhecer a sua arquitetura interna, do ponto de vista de software. Dentro de
um processador existem vários circuitos chamados de registradores. Os
registradores funcionam como posições de memória, porém o seu acesso é
extremamente rápido, muito mais veloz que o da cache L1. O número de
bits dos registradores depende do processador.

 Processadores de 8 bits usam registradores de 8 bits


 Processadores de 16 bits usam registradores de 16 bits
 Processadores de 32 bits usam registradores de 32 bits
 Processadores de 64 bits usam registradores de 64 bits

A figura 1 mostra os registradores internos dos processadores 8086, 8088 e


80286, todos de 16 bits. Todos os processadores têm uma linguagem baseada
8-2 Hardware Total
em códigos numéricos na memória. Cada código significa uma instrução. Por
exemplo, podemos ter uma instrução para somar o valor de AX com o valor
de BX e guardar o resultado em AX. As instruções do processador que
encontramos na memória são o que chamamos de linguagem de máquina.
Nenhum programador consegue criar programas complexos usando a
linguagem de máquina, pois ela é formada por códigos numéricos. É
verdade que alguns programadores conseguem fazer isso, mas não para
programas muito longos, pois tornam-se difíceis de entender e de gerenciar.
Ao invés disso, são utilizados códigos representados por siglas. As siglas são
os nomes das instruções, e os operandos dessas instruções são os
registradores, valores existentes na memória e valores constantes.

Figura 8.1
Registradores internos do
processador 8086.

Por exemplo, a instrução que acabamos de citar, que soma o valor dos
registradores AX e BX e guarda o resultado em AX, é representada por:

ADD AX,BX

Esta instrução é representada na memória pelo seguinte código de máquina:

01 D8

Portanto a instrução ADD AX,BX é representada na memória por dois


bytes, com valores 01 e D8 (em hexadecimal). Os bytes na memória que
formam os programas são o que chamamos de linguagem de máquina. Esse
códigos são lidos e executados pelo processador. Já as representações por
siglas, como “ADD AX,BX”, formam o que chamamos de linguagem
assembly. Quando programamos em linguagem assembly, estamos utilizando
Capítulo 8 – Arquitetura de processadores 8-3
as instruções nativas do processador. A linguagem assembly é usada para
escrever programas que têm contato direto com o hardware, como o BIOS e
drivers. O assembly também é chamado linguagem de baixo nível, pois
interage intimamente com o hardware. Programas que não necessitam deste
contato direto com o hardware não precisam ser escritos em assembly, e são
em geral escritos em linguagens como C, Pascal, Delphi, Basic e diversas
outras. Essas são chamadas linguagens de alto nível. Nas linguagens de alto
nível, não nos preocupamos com os registradores do processador, nem com
a sua arquitetura interna. Os programas pensam apenas em dados, matrizes,
arquivos, telas, etc.

Apresentamos abaixo um pequeno trecho de um programa em linguagem


assembly. Em cada linha deste programa temos na parte esquerda, os
endereços, formados por duas partes (segmento e offset). A seguir temos as
instruções em códigos de máquina, e finalmente as instruções em assembly.

Endereço Código Assembly


--------- ------------- ------------------------------
1B8D:0100 01D8 ADD AX,BX
1B8D:0102 C3 RET
1B8D:0103 16 PUSH SS
1B8D:0104 B03A MOV AL,3A
1B8D:0106 380685D5 CMP [D585],AL
1B8D:010A 750E JNZ 011A
1B8D:010C 804E0402 OR BYTE PTR [BP+04],02
1B8D:0110 BF86D5 MOV DI,D586
1B8D:0113 C6460000 MOV BYTE PTR [BP+00],00
1B8D:0117 E85F0B CALL 0C79
1B8D:011A 8B7E34 MOV DI,[BP+34]
1B8D:011D 007C1B ADD [SI+1B],BH

Quando estamos programando em linguagem assembly, escrevemos apenas


os nomes das instruções. Depois de escrever o programa, usando um editor
de textos comum, usamos um programa chamado compilador de linguagem
assembly, ou simplesmente, Assembler. O que este programa faz é ler o
arquivo com as instruções (arquivo fonte) e gerar um arquivo contendo
apenas os códigos das instruções, em linguagem de máquina (arquivo
objeto). O arquivo objeto passa ainda por um processo chamado link edição,
e finalmente se transforma em um programa, que pode ser executado pelo
processador. O Assembler também gera um arquivo de impressão, contendo
os endereços, códigos e instruções em assembly, como no trecho de listagem
que mostramos acima. O programador pode utilizar esta listagem para
depurar o programa, ou seja, testar o seu funcionamento.
8-4 Hardware Total
Os códigos hexadecimais que representam as instruções do processador são
chamados de opcodes. As siglas que representam essas instruções são
chamadas de mnemônicos.

Daremos neste capítulo, noções básicas da linguagem assembly dos


processadores modernos. Não ensinaremos a linguagem a fundo, mas o
suficiente para você entender como os processadores trabalham. Como a
programação nos processadores modernos é relativamente complexa,
começaremos com o 8080, de 8 bits. A arquitetura do 8080 deu origem à do
8086, que por sua vez deu origem ao 386 e aos processadores modernos.
Entendendo o 8080, que é bem mais simples, será mais fácil entender os
processadores modernos.

Linguagem Assembly 8080


Aprender assembly do 8080 não é uma inutilidade, por duas razões.
Primeiro porque você entenderá com muito mais facilidade o assembly dos
processadores modernos, que afinal foram inspirados no 8080. Segundo que
nem só de PCs vive um especialista em hardware. Você poderá trabalhar
com placas controladoras que são baseadas nos processadores 8051 e Z80.
Ambos são de 8 bits e também derivados do 8080, e são bastante utilizados
em projetos modernos.

A figura 2 mostra os registradores internos do 8080. São registradores de 8


bits, com exceção do PC (Program Counter) e do SP (Stack Pointer), que
têm 16 bits.

Figura 8.2
Registradores internos do 8080.

O registrador mais importante é o acumulador. Ele é o valor de saída da


unidade lógica e aritmética (ALU), na qual são realizadas todas as operações.
Processadores atuais permitem fazer operações com todos os registradores,
mas no 8080, o acumulador deve obrigatoriamente ter um dos operandos, e
sempre é onde ficam os resultados.

Os registradores B, C, D, E, H e L são de uso geral. Servem como operandos


nas operações lógicas e aritméticas envolvendo o acumulador. O PC é um
registrador de 16 bits, e seus valores são usados para formar o barramento de
Capítulo 8 – Arquitetura de processadores 8-5
endereços do processador durante as buscas de instruções. O PC tem
sempre o endereço da próxima instrução a ser executada.

O SP (Stack Pointer) é muito importante. Ele serve para endereçar uma área
de memória chamada stack (pilha). A pilha serve para que os programas
possam usar o que chamamos de subrotinas, que são trechos de programa
que podem ser usados em vários pontos diferentes. Por exemplo, se em um
programa é preciso enviar caracteres para o vídeo, não é preciso usar em
vários pontos deste programa, as diversas instruções que fazem este trabalho.
Basta fazer uma subrotina com essas funções e “chamá-la” onde for
necessária. A subrotina deve terminar com uma instrução RET, que faz o
programa retornar ao ponto no qual a subrotina foi chamada. Para chamar
uma subrotina, basta usar a instrução CALL. Quando esta instrução é
executada, é automaticamente armazenado na pilha, o endereço da instrução
imediatamente posterior à instrução CALL (endereço de retorno). Subrotinas
podem chamar outras subrotinas, permitindo assim criar programas mais
complexos. O Stack Pointer sempre aponta para o topo da pilha, e é
automaticamente corrigido à medida em que são usadas instruções CALL e
RET. A instrução RET consiste em obter o endereço de retorno existente no
topo da pilha e copiá-lo para o PC (Program Counter). Isso fará com que o
programa continue a partir da instrução posterior à instrução CALL.

Os FLAGS são um conjunto de 8 bits que representam resultados de


operações aritméticas e lógicas. São os seguintes esses bits:

Símbolo Nome Descrição


Z Zero Indica se o resultado da operação foi zero
CY Carry Indica se uma operação aritmética teve “vai um” ou “pede emprestado”
P Parity Indica a paridade do resultado da operação.
S Signal Indica o sinal de uma operação, se foi positivo ou negativo
AC Aux. Carry Carry auxiliar, em algumas instruções especiais.

Apesar de ser um processador de 8 bits, o 8080 é capaz de realizar algumas


operações de 16 bits. Nessas operações, os registradores B e C são tratados
como um valor de 16 bits. O mesmo ocorre com o par D/E e H/L.

Além de manipular os registadores, o 8080 também permite obter valores na


memória. Esses valores podem ser de 8 ou 16 bits, e nas instruções que
fazem esses acessos, basta indicar o endereço de 16 bits da posição de
memória que desejamos acessar. Além disso é possivel usar os registradores
HL, BC e DE como apontadores para posições de memória. Nas instruções
do assembly do 8080, o pseudo registrador M é na verdade a posição de
memória (8 bits) cujo endereço está em HL.
8-6 Hardware Total

Programar em assembly do 8080 consiste em utilizar suas instruções,


manipulando seus registradores para executar as funções que desejamos.

Instruções de movimentação de dados


MOV: Move dados entre dois registradores diferentes. Assim como na
maioria das instruções que envolvem registradores, podemos usar M como
sendo a posição de memória apontada por HL. Exemplos:

MOV A,C ; A=C


MOV C,E ; C=E
MOV D,M ; D=M, ou seja, a posição de memória indicada
; por HL
MOV M,A ; M=A

Note que quando escrevemos programas em assembly, podemos usar


comentários em cada linha, bastando usar um ponto-e-vírgula após a
instrução. Tudo o que estiver depois do ponto-e-vírgula será ignorado pelo
assembler. Aqui aproveitamos este convenção para colocar também
comentários explicativos nas instruções de nossos exemplos.

MVI: Carrega um valor constante de 8 bits em um registrador de 8 bits ou


na posição de memória apontada por HL. Exemplos:

MVI C,200 ; Carrega o registrador C com 200 (decimal)


MVI A,15h ; Carrega o acumulador com 15 hexadecimal
MVI M,150 ; Armazena o valor 150 em [HL]
MVI L,32 ; Carrega o registrador L com 32 em decimal

Aproveitamos para além de exemplificar essas instruções, apresentar mais


algumas convenções usadas na linguagem assembly. Os números podem ser
representados nos formatos binário, octal, hexadecimal ou decimal. Quando
não usamos sufixos após os números, considera-se que são números
decimais. Para números hexadecimais, usamos o sufixo H. Quando um
número hexadecimal começa com A, B, C, E, E ou F, temos que usar um
“0” no início, para que o assembler não pense que se trata de uma variável, e
não um número. Números binários devem terminar com “b”, e números
octais devem terminar com “q”. Exemplos:

190
10010111b
325q
8BC3h
Capítulo 8 – Arquitetura de processadores 8-7
Os quatro números acima estão expressos respectivamente em decimal,
binário, octal e hexadecimal.

Outra convenção que vamos introduzir aqui é usar o símbolo [HL] para
indicar a posição de memória cujo endereço é dado por HL. Na linguagem
assembly do 8080, este é o papel do símbolo M. Não usamos [HL], porém
esta convenção foi adotada no assembly do 8086 e outros processadores mais
novos. Da mesma forma vamos usar os símbolos [BC] e [DE] para indicar as
posições de memória apontadas por BC e por DE.

LXI: Carrega um valor constante de 16 bits em um dos pares BC, DE, HL e


no Stack Pointer. Exemplos:

LXI H,35AFh ; Carega HL com o valor 35AF hexadecimal


LXI D,25100 ; Carrega DE com o valor 25100 decimal
LXI B,0 ; Carrega BC com 0
LXI SP,200 ; Carrega o Stack Pointer com 200 decimal

Note que os números de 8 bits podem assumir valores inteiros positivos de 0


a 255 decimal (ou de 0 a FF em hexadecimal). Os números inteiros positivos
de 16 bits podem assumir valores entre 0 e 65.535 decimal (ou 0 a FFFF
hex).

Obseve a instrução LXI H, 35AFh. Este valor 35AF é formado por 16 bits,
sendo que os 8 bits mais significativos têm o valor 35 hex, e os 8 bits menos
significativos têm o valor AF hex. No par HL, o registrador H é o mais
significativo, e o registrador L é o menos significativo. Sendo assim o
registrador H ficará com 35 hex e o registrador L ficará com AF hex.

LDA e STA: A instrução LDA carrega o acumulador (registrador A) com o


valor que está no endereço de memória especificado. A instrução STA faz o
inverso, ou seja, guarda o valor de A na posição de memória especificada.
Exemplos:

LDA 1000h ; Carrega A com o valor existente em [1000h]


STA 2000h ; Guarda o valor de A em [2000h]

Estamos utilizando a partir de agora a notação [nnnn] para indicar a posição


de memória cujo endereço é nnnn. Esta notação não é usada no assembly
para 8080, mas é usada no assembly do 8086 e superiores.

LHLD e SHLD: A instrução LHLD carrega nos registradores H e L, o valor


de 16 bits existente nas duas células de memória cujo endereço é
especificado. A instrução SHLD faz o inverso. Exemplos:
8-8 Hardware Total

LHLD 1000h ; Faz L=[1000h] e H=[1001h]


SHLD 2000h ; Guarda L em [2000h] e H em [2001h]

Aqui vai mais um conceito importante. A memória do 8080 é uma sucessão


de bytes, mas podemos também acessar words, ou seja, grupos de 16 bits. A
operação envolve dos bytes consecutivos, e nas instruções indicamos apenas
o endereço do primeiro byte. Os 8 bits menos significativos estão
relacionados com a posição de memória indicada, e os 8 bits seguintes estão
relacionados com a próxima posição. A figura 3 ilustra o que ocorre ao
usarmos a instrução SHLD 2000h, levando em conta que H está com o valor
35h e L com o valor 8Ch.

Figura 8.3
Armazenando HL em [2000h].

Esta convenção é utilizada por todos os processadores Intel. Sempre que é


feita uma leitura ou escrita na memória, as partes menos significativas dizem
respeito aos endereço menores, e as partes mais significativas correspondem
aos endereços maiores.

LDAX e STAX: Essas instruções fazem respectivamente operações de load


(carrega) e store (guarda) do acumulador, usando a posição de memória cujo
endereço está no par BC ou DE. Exemplos:

LDAX D ; A = [DE]
STAX B ; [BC] = A
LDAX B ; A = [BC]
STAX D ; [DE] = A

Note que estamos usando as notações [BC] e [DE] para indicar as posições
de memória cujos endereços são dados por BC e DE. Observe que as
instruções LDAX H e STAX H não existem, mas em seu lugar temos “MOV
A,M” e “MOV M,A” que fazem a mesma coisa.
Capítulo 8 – Arquitetura de processadores 8-9
XCHG: Troca o valor de HL com o valor de DE. Esta instrução só é usada
na forma:

XCHG ; DE  HL

Trecho de programa com movimentação de dados


Mostraremos agora uma seqüência de instruções de movimentação de dados
apresentadas aqui. Usaremos depois de cada instrução, um ponto-e-vírgula,
seguido de comentários. Esta é uma prática comum nos programas em
assembly e em outras linguagens. O comentário não produz instruções para
o processador, apenas serve para o programador explicar melhor o seu
programa.

INIC: MVI A,100 ; Carrega A com o valor 100 decimal


MOV C,A ; Copia o valor de A para o registrador C
LXI H, 300h ; Carrega HL com o valor 300h. H ficará com 03 e L ficará com 00
MVI M,40 ; Armazena no endereço 300h (apontado por HL), o valor 40
LXI D, 1000h ; Carrega DE com o valor 1000h
MOV A,M ; Move para A o valor armazanedo no endreço 300h
STAX D ; Guarda o valor de A na posição 1000h da memória
SHLD 2000h ; Guarda o valor de HL nas posições 2000 e 2001
XCHG ; Troca os valores de DE e HL

Na listagem acima, “INIC:” é o que chamamos de LABEL. Ele será


entendido pelo assembler como um endereço que deverá ser utilizado
posteriormente em alguma instrução.

Instruções aritméticas
ADD: Soma com A, o valor do registrador especificado, ou da posição de
memória apontada por HL (M). O resultado da operação é armazenado em
A. Exemplos:

ADD B ; A = A+B
ADD C ; A = A=C
ADD L ; A = A+L
ADD M ; A = A+[HL]

Assim como ocorre com todas as instruções aritméticas e lógicas, os flags (Z,
CY, P, S e AC) são atualizados de acordo com o resultado da operação. Por
exemplo, se somarmos C8h com 72h, o resultado será 13Ah. Este valor não
cabe em 8 bits, portanto o resultado será 3Ah e o bit Carry será ligado para
indicar que ocorreu um “vai 1”.
8-10 Hardware Total
ADI. Soma com A, o valor constante especificado. O resultado fica
armazenado em A. Exemplos:

ADI 90 ; A = A+90
ADI 35 ; A = A+35

ADC: Semelhante à instrução ADD, exceto que o bit Carry também é


adicionado. Esta operação serve para fazer somas com “vai 1”. Desta foram
podemos dividir números grande em valores de 8 bits, e somar 8 bits de
cada vez. Sempre que fazemos uma soma, o Carry ficará com o “vai 1” da
operação, e assim poderá ser usado para somar a parcela seguinte.
Exemplos:

ADC L ; A = A+L+carry
ADC D ; A = A+D+carry
ADC M ; A = A+[HL]+carry

ACI: Semelhante à instrução ADI, exceto pelo Carry também entrar na


soma. Exemplos:

ACI 90 ; A = A+90+carry
ACI 84 ; A = A+84+carry

SUB: Faz uma subtração de A com o registrador (A = A-registrador) ou com


M. Exemplos:

SUB D ; A = A-D
SUB C ; A = A-C
SUB M ; A = A-[HL]

Nesta operação, o carry é ligado para indicar o resultado negativo, o que


serve para fazer cálculos com vários dígitos, usando o método de “pedir
emprestado”.

SUI: Subtrai do acumulador, o número especificado. Por exemplo:

SUI 20 ; A = A-20
SUI 82 ; A = A-82
SUI 0DFh ; A = A-DF (hex)

SBB: Similar à instrução SUB, exceto que leva em conta o valor do carry.
Serve para fazer cálculos com o método de “pedir emprestado”. Exemplos:

SBB C ; A = A-C-carry
SBB L ; A = A-L-carry
SBB M ; A = A-[HL]-carry
Capítulo 8 – Arquitetura de processadores 8-11

SBI: Similar à instrução SUI, exceto que leva em conta o valor do carry.
Serve para fazer cálculos com o método de “pedir emprestado”. Exemplos:

SBI 2Fh ; A = A-2Fh-carry


SBI 73h ; A = A-73h-carry

INR: Incrementa em uma unidade o valor do registrador especificado. Serve


para implementar contadores. Exemplos:

INR A ; A = A+1
INR C ; C = C+1
INR D ; D = D+1
INR L ; L = L+1
INR M ; [HL] = [HL]+1

Quando o valor do registrador é FF e usamos esta instrução, ele passará a


ficar com o valor 00 e o bit Carry será ligado. O bit Zero também será
ligado, indicando que o resultado da operação foi zero.

DCR: Diminui de uma unidade o conteúdo do registrado especificado. Esta


instrução é usada para implementar contagens decrescentes. Exemplos:

DCR A ; A = A-1
DCR C ; C = C-1
DCR D ; D = D-1
DCR H ; H = H-1
DCR M ; [HL] = [HL]-1

Quando o registrador tem o valor 1 e usamos esta instrução, o seu conteúdo


passará a ser 00. O bit Zero será ligado, indicando que o resultado da
operação foi 0. Se o registrador estiver com o valor 00 e usarmos novamente
esta instrução, seu valor passará a ser FF. O bit Carry será ligado, para
indicar que o resultado tem valor negativo.

INX e DCX: Essas instruções são similares às instruções INR e DCR, exceto
que operam com pares de registradores (BC, DE e HL) e com o Stack
Pointer. Não podem ser usadas diretamente para implementar contadores,
pois elas não afetam os valores dos flags, ou seja, não “avisam” se o resultado
foi zero, positivo ou negativo. Essas instruções não tinham objetivo de fazer
contagem, mas sim de usar os registradores como ponteiros para a memória.
Ao lançar o 8086, a Intel corrigiu este “deslize”. As instruções
correspondentes nos processadores de 16 bits afetam os valores dos flags, o
que é importante para tomar decisões posteriores em função do resultado da
contagem. Exemplos:
8-12 Hardware Total

INX H ; HL = HL+1
INX D ; DE = DE+1
DCX B ; BC = BC-1
INX SP ; SP = SP+1

Note que apesar do 8080 ser um processador de 8 bits, INX e DCX são
consideradas instruções de 16 bits.

DAA: Esta instrução é usada na manipulação de números codificados no


formato BCD (Bynary Coded Decimal). Nesta representação, um valor de 8
bits é dividido em 2 grupos de 4 bits. Um grupo de 4 bits representa o dígito
das unidades e o outro grupo representa o dígito das dezenas, no formato
decimal. Números neste formato podem ser somados e subtraídos pelas
mesmas instruções que manipulam números binários. A única diferença é
que no final da operação é preciso usar a instrução DAA para fazer o ajuste
decimal. Por exemplo, se quisermos somar os números 48 e 36 (BCD),
usamos as instruções comuns (ADD, ADI, etc.) e encontraremos o resultado
7E (hex). Entretanto o resultado esperado, considerando o formado BCD,
seria 84 (pensando em decimal). Logo após fazer a soma, se usarmos a
instrução DAA, aquele valor 7E resultará em 84.

DAD: Esta é uma operação de soma em 16 bits. Soma o valor de 16 bits


presente em HL com o par de registradores especificado. Este “par” pode ser
BC, DE, HL ou SP. O resultado é colocado em HL, e o bit Carry é afetado,
refletindo um eventual “vai 1”. Exemplos:

DAD B ; HL = HL+BC
DAD D ; HL = HL+DE
DAD H ; HL = HL+HL
DAD SP ;HL = HL+SP

Para exemplificar as instruções apresentadas até aqui, vamos mostrar um


exemplo no qual movemos 30 bytes localizados a partir do endereço 1000h
para o endereço 2000h.

LXI H, 1000h ; HL vai apontar para a origem


LXI D, 2000h ; DE vai apontar para o destino
MVI C, 30 ; C será usado como contador:
TRAN: MOV A,M ; Pega o byte da origem
STAD X ; Guarda no destino
INX H ; Aponta para o próximo byte
INX D ; Aponte para o próximo byte
DCR C ; Decrementa o contador
JNZ TRAN ; Vai para TRAN se não chegou a ZERO
Capítulo 8 – Arquitetura de processadores 8-13
Além das instruções já conhecidas, estamos usando a instrução JNZ (Jump if
not Zero). Este instrução é um exemplo de desvio condicional. O programa
continuará a partir do endereço TRAN caso o bit ZERO não esteja ativado,
ou seja, se o resultado da operação anterior (C=C-1) não foi zero. Quando a
contagem chegar a zero, a instrução JNZ não provocará o desvio, e o
programa continuará com a instrução seguinte.

Instruções lógicas
As instruções lógicas são necessárias para que os programas possam tomar
decisões em função dos dados. São instruções que realizam operações AND,
OR, XOR (ou exclusivo) e NOT (negação). Existem ainda instruções de
comparação, instruções para manipular o bit carry e instruções para rotação
de bits.

Para entender o funcionamento dessas instruções, temos que lembrar as


tabelas verdade dos operadores NOT, AND, OR e XOR:

X NOT X X Y X AND Y X Y X OR Y
0 1 0 0 0 0 0 0
1 0 0 1 0 0 1 1
1 0 0 1 0 1
1 1 1 1 1 1

Como vemos na tabela acima, o operador NOT faz a inversão do bit sobre o
qual atua. O operador AND dará resultado 1 apenas quando ambos os bits
forem 1, e dará 0 em caso contrário. O operador OR dará resultado 0
somente quando ambos os bits forem 0. O operador XOR dará resultado 1
se os dois bits forem diferentes, e 0 se ambos os bits forem iguais.

X Y X XOR Y
0 0 0
0 1 1
1 0 1
1 1 0

Essas operações são envolvem apenas um bit, mas nas instruções lógicas dos
processadores, atuam individualmente sobre cada um dos bits. Por exemplo,
se calcularmos 10111110 AND 10010101, teremos o seguinte resultado:

10111110
10010101 AND
------------
10010100
8-14 Hardware Total
Note que o primeiro bit do resultado é obtido fazendo a operação AND com
os primeiros bits das duas parcelas, e assim por diante.

ANA e ANI: Realiza uma operação AND, bit a bit, do acumulador com o
registrador especificado. O resultado da operação fica no acumulador. A
instrução ANI faz o AND do acumulador com um valor constante.

ANA B ; A = A AND B
ANA C ; A = A AND C
ANA A ; A = A AND A
ANA M ; A = A AND [HL]
ANI 3Fh ; A = A AND 3F

Uma das várias aplicações desta instrução é testar se determinados bits são
zero ou 1. Por exemplo, se fizermos ANI 00000100b, podemos usar a seguir
uma instrução JZ ou JNZ que causarão desvio ou não dependendo do fato
do bit 2 estar ligado ou desligado.

Chegou a hora de apresentar mais um conceito: a identificação dos bits. Em


um grupo de 8 bits, chamamos cada um desses bits, da direita para a
esquerda, de bit 0, bit 1, até bit 7, ou seja:

bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0

ORA, ORI, XRA, XRI: ORA faz a operação OR do acumulador com o


registrador especificado; ORI faz o mesmo com um valor constante; XRA faz
a operação XOR (OU Exclusivo) do acumulador com o registrador
especificado, XRI faz o mesmo com um valor constante. Exemplos:

XRA B ; A = A XOR B
XRA C ; A = A XOR C
ORA L ; A = A XOR L
ORI 20h ; A = A OR 20h
XRI 04h ; A = A XOR 04h
XRA A ; A = A XOR A

Aproveitamos para mostrar alguns macetes de programação assembly. A


instrução ORI serve para ligar um bit selecionado. Para ligar os bits 7, 6, 5, 4,
3, 2, 1 e 0 basta fazer um ORI com valores 80h, 40h, 20h, 10h, 8, 4, 2 e 1,
respectivamente. A instrução XRI fará a inversão do bit correspondente (use
os mesmos valores que indicamos para a instrução ORI). A instrução XRA
A tem o efeito de zerar o acumulador.

CMP, CPI: A instrução CMP compara o acumulador com outros


registradores. A instrução CPI compara o acumulador com um valor
Capítulo 8 – Arquitetura de processadores 8-15
constante de 8 bits. O resultado do acumulador não é afetado. As instruções
apenas afetam os bits Zero e Carry. Após essas instruções podem ser usados
desvios condicionais que testam esses dois bits. Esses bits ficarão ligados ou
desligados de acordo com os valores comparados:

A maior que Valor Z=0 Cy=0


A igual a Valor Z=1 Cy=0
A menor que Valor Z=0 Cy=1

Exemplos:

CMP C ; Compara A com C


CMP L ; Compara A com L
CMP M ; Compara A com [HL]
CPI 4Ch ; Compara A com 4C

RLC, RRC: Essas duas instruções são usadas para deslocar os bits do
acumulador. RLC desloca para esquerda e RRC desloca para a direita. A
operação é mostrada na figura 4.

Figura 8.4
Instruções RLC e RRC.

Na instrução RLC, cada bit assume o lugar do bit imediatamente à sua


esquerda (ou seja, o bit imediatamente mais significativo). O bit 7 é
transferido para o bit 0, e uma cópia do bit 7 é feita no Carry. Na instrução
RRC, o deslocamento é feito de forma inversa. O bit 0 é copiado para o bit
7 e para o Carry. Essas instruções têm diversas aplicações, entre as quais, a
implementação de operações de multiplicação e divisão, já que o 8080 não
as possui no seu conjunto de instruções.

Essas instruções não têm parâmetros. São usadas simplesmente nas formas
RRC e RLC.

RAL, RAR: Também fazem deslocamentos dos bits do acumulador, para a


esquerda e para a direita. A diferença é que neste caso, a rotação é feita com
8-16 Hardware Total
9 bits, sendo 8 do acumulador e mais o Carry. A operação dessas instruções
é mostrada na figura 5.

Figura 8.5
Instruções RAL e RAR.

CMA: Complementa o acumulador, ou seja, faz a inversão de todos os seus


bits.

STC, CMC: Essas instruções servem para carregar valores no Carry. A


instrução STC faz Carry=1, e a instrução CMC inverte o valor do Carry.
Note que não existe uma instrução para zerar o Carry. Ao invés dela,
podemos usar STC seguida de CMC, ou então usar a instrução ANA A ou
ORA A, que não alteram o valor de A mas zeram o Carry.

Instruções de desvio
As instruções de desvio são importantíssimas, e são executadas o tempo
todo. O processador tende a seguir uma seqüência de instruções, na mesma
ordem na qual são encontradas na memória, ou seja, depois de cada
instrução é executada a instrução seguinte. Um programa que só executa
instruções na sequência não tem muita utilidade. Todos os processadores
precisam de insruções de desvio, que fazem com que a execução seja
continuada a partir de um outro ponto qualquer do programa. Já mostramos
um exemplo de trecho de programa que usa a instrução JNZ (jump if not
zero) para implementar a repetição de um trecho um certo número de vezes.
Um trecho de programa que é executado diversas vezes é chamado de
LOOP.

JMP: Esta é a principal e mais simples instrução de desvio. É o que


chamamos de desvio incondicional, ou seja, sempre será executada, não
importa em que condições. Por exemplo, ao encontrar a instrução JMP
8000h, o processador continuará a execução do programa a partir das
instruções localizadas no endereço 8000h.
Capítulo 8 – Arquitetura de processadores 8-17
CALL e RET: A instrução CALL também é um desvio, mas bem diferente
do JMP. É uma chamada de rotina. Uma rotina é um trecho de programa
que pode ser chamado de vários pontos de um programa principal. No final
de uma rotina deve exitir uma instrução RET, que faz o processador retornar
ao ponto imediatamente após a instrução CALL.

Comparando as instruções CALL e JMP, a única diferença é que no caso da


instrução CALL, o endereço da próxima instrução (endereço de retorno) é
guardado no topo da pilha. O valor do SP (stack pointer é atualizado para
permitir novos empilhamentos). A instrução RET simplesmente obtem o
endereço no topo da pilha e o coloca em PC (Program Counter), fazendo
com que a execução continue de onde parou.

Mostraremos novamente nosso pequeno trecho de programa que move um


certo número de bytes de uma parte para outra da memória, mas desta vez
usando uma rotina. O trecho começa no endereço 1000, onde carregamos o
par HL com o endereço da origem, DE com o endereço do destino e o
registrador C com o número de bytes (no caso são 16 bytes, que
corresponde a 10 em hexadecimal). A seguir é chamada uma rotina que está
no endereço 1020. Esta é a rotina responsável pela movimentação. Depois
que a rotina é chamada, a próxima instrução a ser executada é JMP 0, que
está no endereço 100A.

1000: 21 00 81 LXI H, 8100


1003: 11 00 82 LXI D, 8200
1006: 0E 10 MVI C,10
1008: CD 20 10 CALL 1020
100A: C3 00 00 JMP 0
...
1020: 7E MOV A,M
1021: 12 STAX D
1022: 23 INX H
1023: 13 INX D
1024: 0D DCR C
1025: C2 20 10 JNZ 1020
1028: C9 RET

Observe que a rotina de movimentação localizada no endereço 1020 é


genérica. Ela pode mover dados entre duas posições quaisquer de memória,
dadas por HL e DE. O número de bytes também pode ser qualquer (de 1 a
255), e deve ser dado em C. Dizemos então que HL, DE e C são os
parâmetros desta rotina.

É importante entender o que acontece com a stack ao executarmos


instruções CALL e RET. Na instrução CALL, o endereço de retorno e
empilhado. Na instrução RET, o endereço de retorno é desempilhado. No
8-18 Hardware Total
trecho de programa mostrado acima, a instrução CALL causará o
empilhamento do endereço 100A, que é o endereço da instrução seguinte, e
será o endereço de retorno.

Figura 8.6
Empilhamento de um endereço de
retorno na stack, feito por uma instrução
CALL.

A figura 6 ilustra o que está ocorrendo. Digamos que o registrador SP (Stack


Pointer) esteja com o valor inicial 0100. A stack aumenta para trás, ou seja,
para endereços menores. Ao executar a instrução CALL, o processador
empilhará o endrereço 100A nos bytes imediatamente anteriores ao
endereço indicado por SP. Portanto ocupará os endereços 00FF e 00FE. O
SP será atualizado para 00FE, que será o novo topo da pilha. Assim novos
endereços poderão ser empilhados quando forem executadas outras
instruções CALL.

A instrução RET fará exatamente o inverso do mostrado na figura 6. O Stack


Pointer estará com o valor 00FE, portanto irá obter o endereço de retorno
nas posições 00FE e 00FF da memória, e encontrará 100A. O Stack Poitner
será então atualizado para 0100, que será o novo topo da pilha.

JMPs, CALLs e RETs condicionais – Além das instruções JMP, CALL e


RET, que são incondicionais, existem suas versões condicionais, que são
executadas apenas quando uma determinada condição é satisfeita. Essas
condições são baseadas nos flags: Zero, Carry, Parity e Signal. São elas:

Instrução Ação Interpretação


JZ Pula se Zero está ligado Pula se o resultado é zero,
Pula se iguais
JNZ Pula se Zero está desligado Pula se o resultado não é zero,
Pula se diferentes
JC Pula se Carry está ligado Pula se menor, pula se carry
JNC Pula se Carry está desligado Pula se maior ou igual, pula se não carry
JPE Pula se paridade Par Pula se número de bits 1 é par
JPO Pula se paridade Ímpar Pula se número de bits 1 é ímpar
JP Pula se sinal positivo Pula se resultado positivo ou zero
JM Pula se sinal negativo Pula se resultado negativo
Capítulo 8 – Arquitetura de processadores 8-19
Nesta tabela mostramos a ação de cada uma desas instruções, e ainda uma
interpretação dessas ações. Por exemplo, a instrução JZ pode ser usada logo
depois uma operação aritmética e queremos que seja feito o desvio se o
resultado foi zero. Pode ainda ser usada depois de uma comparação e
queremos que o desvio seja feito se os valores comparados forem iguais.

Da mesma forma existem as chamadas condicionais de rotinas e os retornos


condicionais:

CZ, CNZ, CC, CNC, CPE, CPO, CP, CM


RZ, RNZ, RC, RNC, RPE, RPO, RP, RM

RST n: Esta instrução é similar a uma instrução CALL. A diferença é que


ela não precisa que seja indicado o endereço, pois está implícito. Podemos
usá-la de 8 formas diferentes:

RST 0 / RST 1 / RST 2 / RST 3 / RST 4 / RST 5 / RST 6 / RST 7

Essas instruções têm o mesmo efeito que:

CALL 0000 / CALL 0008 / CALL 0010 / CALL 0018 / … / CALL 0038

O objetivo dessas instruções é economizar bytes, já que ao invés dos 3 bytes


ocupados por uma instrução CALL, usa apenas um byte. Quando uma certa
rotina é usada muitas vezes em um programa, podemos colocá-la a partir de
um desses endereços e chamá-las através das instruções RST. Note que esses
endereços estão separados apenas por 8 bytes, portanto não é possível
colocar diretamente neles, rotinas maiores que este tamanho. O que
normalmente fazemos é usar nesses endereços, instruções de JMP para outra
área onde ficam as rotinas.

PCHL: Carrega em PC o valor existente em HL. Isto é equivalente a


executar um JMP para o endereço especificado por HL. É útil quando
queremos desviar para um local variável, em função do valor em HL, obtido
por exemplo, de uma tabela de endereços.

Operações com a pilha, E/S e controle


Para completar o conjunto de instruções do 8080, falta apenas uma pequena
miscelânea de instruções para para manipulação da stack, entrada/saída e
controle.
8-20 Hardware Total
PUSH e POP: Já vimos como a stack é usada para emplilhar e desempilhar
endereços nas instruções CALL e RET. São sempre valores de 16 bits. Além
de endereços, podemos ainda empilar e desempilhar dados na stack. Por
exemplo, a instrução PUSH H guardará o valor de HL no topo da stack. A
instrução POP H fará o inverso, ou seja, retirará da pilha o valor do seu topo
e o copiará para HL. As instruções PUSH e POP podem ser usadas com os
parâmetros B, D e H, que operam com os pares BC, DE e HL. Podemos
ainda usar PUSH PSW, que salva em um grupo de 16 bits, o valor do
acumulador e dos flags. A instrução POP PSW faz o inverso.

XTHL: Lembra da instrução XCHG, que troca os valores de HL e DE? A


instrução XTHL faz algo parecido. Troca os valores de HL e do topo da
pilha.

SPHL: Já vimos também a instrução “LXI SP, Valor”, que carrega um valor
fixo no stack pointer. Isto é necessário na inicialização dos programas,
quando temos que definir onde ficará a pilha. A instrução SPHL é mais
flexível. Ela cria a stack em qualquer ponto da memória, bastando indicar
seu endereço em HL.

IN, OUT: São instruções importantíssimas que servem para o processador


trocar dados com o mundo exterior. Através da instrução IN podemos obter
dados provenientes de interfaces que estão ligadas aos periféricos. O dado
lido ficará no acumulador. A instrução OUT faz o inverso, ou seja, transfere
para o endereço de E/S especificado, o valor que está no acumulador.
Exemplos:

IN 70h ; Lê dado que está no endereço de E/S 70h


OUT 40h ; Envia para o endereço de E/S 40h, o valor de A

DI, EI: Essas instruções controlam as interrupções de hardware. DI faz com


que as interrupções sejam desabilitadas. EI faz com que sejam habilitadas
novamente. São úteis durante o atendimento a interrupções de hardware e
em certos trechos críticos que não podem ser interrompidos.

HLT: Halt. Faz o processador parar de executar instruções. O processador


só sai deste estado quando ocorre uma interrupção.

NOP: No Oparation. Esta instrução não faz nada. É usada quando queremos
fazer uma pausa entre duas instruções seguidas. Normalmente isso é
necessário quando temos um programa que faz controle direto do hardware.
Capítulo 8 – Arquitetura de processadores 8-21
Isto pode ser necessário, por exemplo, para fazer o processador esperar um
pouco mais pela execução das funções de certos circuitos lentos.

Um pequeno programa para 8080


Finalizamos a apresentação das instruções do 8080, mostrando um pequeno
programa. Este programa faz o recebimento dos caracteres do teclado e os
coloca na memória a partir do endereço 1000h. O número máximo de
caracteres que poderá ser recebido é 80. Quando terminarmos de digitar a
linha, devemos teclar ENTER, cujo código hexadecimal é 0Dh. Estamos
supondo aqui que o computador tem um console (teclado/vídeo
combinados) ligado em uma interface serial que ocupa os endereços de E/S
80h e 81h. O endereço 80h é a porta de dados, que envia caracteres do para
o vídeo (escrita) e lê caracteres do teclado (leitura). A porta 81h é usada
como status. Seus bits 0 e 1 indicam respectivamente se a interface tem um
dado vindo do teclado e se está pronta para enviar um dado para o vídeo.

LXI H, 1000h ; Aponta para a área de memória


MVI C,0 ; Zera o contador de bytes
LECH: CALL INCHAR ; Lê caractere do teclado
CPI 0Dh ; Testa se foi ENTER
JZ FIM ; Vai para o fim se teclou ENTER
MOV B,A ; Se não foi enter, guarda caracter em B
MOV A,C ; Pega o contador de caracteres
CPI 80 ; Testa se chegou a 80
JZ LECH ; Se há chegarm 80, ignora e volta a ler
MOV A,B ; Se não chegou a 80, pega o caracter
MOV M,A ; Guarda caracter na memória
CALL OUTCHAR ; Envia o caracter para o vídeo
INR C ; Incrementa o contador de caracteres
INX H ; Incrementa o ponteiro
JMP LECH ; Vai ler o próximo caracter
FIM: JMP 0 ; Pula para 0000 quando terminar o programa
; Rotina de leitura de caracter
INCHAR: IN 81h ; Lê o status da porta serial
ANI 01 ; Testa se o bit 0 está ligado
JZ INCHAR ; Se está desligado continua tentando
IN 80h ; Lê o código do caracter
RET ; e retorna com o caracter em A
; Rotina que envia para o vídeo, caracter
; que está em A
OUTCHAR: PUSH B ; Salva para BC na pilha
MOV B,A ; Guarda em B o caracter
OUTC1: IN 81h ; Lê o status da porta serial
ANI 02 ; Testa o bit 1
JZ OUTC1 ; Se bit 1 está zerado, continua esperando
MOV A,B ; Pega o caracter
OUT 80h ; Envia o caracter
POP B ; Restaura o valor original de BC
RET ; e retorna
8-22 Hardware Total
Códigos das instruções do 8080
Apresentamos a seguir uma tabela com os códigos de todas as instruções do
8080. Não que você vá programar 8080, mas para que você tenha uma idéia
da relação entre as instruções e os seus códigos. Na tabela que se segue,
temos as seguintes convenções:

 D8 representa um dado constante de 8 bits


 D16 representa um dado constante de 16 bits
 Addr representa um endereço de 16 bits

Op Op Op Op Op Op
Cod Mnemonic Code Mnemonic Cod Mnemonic Cod Mnemonic Code Mnemonic Code Mnemonic
e e e
00 NOP 2B DCX H 56 MOV D,M 81 ADD C AC XRA H D7 RST 2
01 LXI B,D16 2C INR L 57 MOV D,A 82 ADD D AD XRA L D8 RC
02 STAX B 2D DCR L 58 MOV E,B 83 ADD E AE XRA M D9 -
03 INX B 2E MVI L,D8 59 MOV E,C 84 ADD H AF XRA A DA JC ADDR
04 INR B 2F CMA 5A MOV E,D 85 ADD L B0 ORA B DB IN D8
05 DCR B 30 - 5B MOV E,E 86 ADD M B1 ORA C DC CC ADDR
06 MVI B,D8 31 LXI SP,d16 5C MOV E,H 87 ADD A B2 ORA D DD -
07 RLC 32 STA ADDR 5D MOV E,L 88 ADC B B3 ORA E DE SBI D8
08 - 33 INX SP 5E MOV E,M 89 ADC C B4 ORA H DF RST 3
09 DAD B 34 INR M 5F MOV E,A 8A ADC D B5 ORA L E0 POR
0A LDAX B 35 DCR M 60 MOV H,B 8B ADC E B6 ORA M E1 POP H
0B DCX B 36 MVI M,D8 61 MOV H,C 8C ADC H B7 ORA A E2 JPO ADDR
0C INR C 37 STC 62 MOV H,D 8D ADC L B8 CMP B E3 XTHL
0D DCR C 38 - 63 MOV H,E 8E ADC M B9 CMP C E4 CPO ADDR
0E MVI C,D8 39 DAD SP 64 MOV H,H 8F ADC A BA CMP D E5 PUSH H
0F RRC 3A LDA ADDR 65 MOV H,L 90 SUB B BB CMP E E6 ANI D8
10 - 3B DCX SP 66 MOV H,M 91 SUB C BC CMP H E7 RST 4
11 LXI D,D16 3C INR A 67 MOV H,A 92 SUB D BD CMP L E8 RPE
12 STAX D 3D DCR A 68 MOV L,B 93 SUB E BE CMP M E9 PCHL
13 INX D 3E MVI A,D8 69 MOV L,C 94 SUB H BF CMP A EA JPE ADDR
14 INR D 3F CMC 6A MOV L,D 95 SUB L C0 RNZ EB XCHG
15 DCR D 40 MOV B,B 6B MOV L,E 96 SUB M C1 POP B EC CPE ADDR
16 MVI D,D8 41 MOV B,C 6C MOV L,H 97 SUB A C2 JNZ ADDR ED -
17 RAL 42 MOV B,D 6D MOV L,L 98 SBB B C3 JMP ADDR EE XRI D8
18 - 43 MOV B,E 6E MOV L,M 99 SBB C C4 CNZ ADDR EF RST 5
19 DAD D 44 MOV B,H 6F MOV L,A 9A SBB D C5 PUSH B F0 RP
1A LDAX D 45 MOV B,L 70 MOV M,B 9B SBB E C6 ADI D8 F1 POP PSW
1B DCX D 46 MOV B,M 71 MOV M,C 9C SBB H C7 RST 0 F2 JP ADDR
1C INR E 47 MOV B,A 72 MOV M,D 9D SBB L C8 RZ F3 DI
1D DCR E 48 MOV C,B 73 MOV M,E 9E SBB M C9 RET F4 CP ADDR
1E MVI E,D8 49 MOV C,C 74 MOV M,H 9F SBB A CA JZ ADDR F5 PUSH PSW
1F RAR 4A MOV C,D 75 MOV M,L A0 ANA B CB - F6 ORI D8
20 - 4B MOV C,E 76 HLT A1 ANA C CC CZ ADDR F7 RST 6
21 LXI H,D16 4C MOV C,H 77 MOV M,A A2 ANA D CD CALL Addr F8 RM
22 SHLD ADDR 4D MOV C,L 78 MOV A,B A3 ANA E CE ACI D8 F9 SPHL
23 INX H 4E MOV C,M 79 MOV A,C A4 ANA H CF RST 1 FA JM ADDR
24 INR H 4F MOV C,A 7A MOV A,D A5 ANA L D0 RNC FB EI
25 DCR H 50 MOV D,B 7B MOV A,E A6 ANA M D1 POP D FC CM ADDR
26 MVI H,D8 51 MOV D,C 7C MOV A,H A7 ANA A D2 JNC ADDR FD -
27 DAA 52 MOV D,D 7D MOV A,L A8 XRA B D3 OUT D8 FE CPI D8
Capítulo 8 – Arquitetura de processadores 8-23
28 - 53 MOV D,E 7E MOV A,M A9 XRA C D4 CNC ADDR FF RST 7
29 DAD H 54 MOV D,H 7F MOV A,A AA XRA D D5 PUSH D
2A LHLD ADDR 55 MOV D,L 80 ADD B AB XRA E D6 SUI D8

Observe que alguns códigos, ao serem recebidos pelo processador, não


representam instrução alguma. No caso do 8080, esses códigos são:

08, 10, 18, 20, 28, 30, 38, CB, D9, DD, ED e FD.

Ao encontrar uma dessas instruções inválidas, o 8080 não fazia nada. Alguns
ciriosos descobriram que certos códigos inválidos eram na verdade instruções
não documentadas da Intel, porém nenhum programador sério ousava
utilizá-las. Como eram instruções não oficiais, não era garantido que fossem
implementadas em todas as versões do processador. No 8085, uma evolução
do 8080, a Intel utilizou duas novas instruções: RIM (20h) e SIM (30h). A
Zilog utilizou esses códigos para usar com as novas instruções do seu
processador Z80.

Nos processadores modernos, não é permitido executar instruções inválidas.


Quando isso ocorre, o próprio processador gera uma interrupção e indica
operação ilegal. No Windows, isso resulta em uma mensagem como: Erro o
programa xxxx executou uma operação ilegal em .....”

Linguagem Assembly do 8086


Depois desta breve apresentação do assembly do processador 8080, estamos
finalmente entrando na era dos PCs, com o assembly do processador 8086.
Os seus registradores internos são de 16 bits, mas foram inspirados nos
registradores do 8080. Na figura 7, os registradores indicados em branco são
“herdados” do 8080, enquanto os indicados em cinza são novos, próprios do
8086.
8-24 Hardware Total
Figura 8.7
Registradores internos do
8086.

Os regisradores AX, BC, CX e DX são de 16 bits, mas podem ser tratados


como duas partes de 8 bits. AX é dividido em AH e AL, BX é dividido em
BH e BL, e assim por diante. AX é o acumulador, portanto AL corresponde
ao registrador A do 8080. O registrador BX do 8086 corresponde ao par HL
do 8080 (assim como BH corresponde a H e BL corresponde a L). Da
mesma forma, CX corresponde ao par BC e DX corresponde ao par DE. O
contador de programa (PC) do 8080 é chamado no 8080 de IP (Instruction
Pointer). O Stack Pointer (SP) é similar, e os flags (Cy, Z, AC, P e S) ficam
em um registrador de flags, com 16 bits.

Esta correspondência entre os registradores do 8086 e do 8080 foi proposital.


Permitiu que programas escritos em assembly do 8080 fossem rapidamente
convertidos para o 8086, mesmo que não da forma mais eficiente. Por
exemplo, as instruções MOV D,B / MOV E,C podiam ser diretamente
traduzidas por MOV DH,CH / MOV DL,CL. Entretanto é muito melhor
usar os recursos de 16 bits, com a instrução MOV DX,CX. Depois de
converter os antigos programas assembly de 8080 para 8086, os produtores
de software passaram a criar programas novos já usando os recursos mais
avançados do 8086, resultando em programas mais eficientes. Programas em
linguagem de alto nível (C, Pascal, etc.) podiam ser convertidos com mais
facilidade, já que eram desvinculados do assembly.

Novas instruções
Além de ter todas as instruções do 8080 ou instruções similares, o 8086
trouxe novas instruções bem mais avançadas, com execução mais rápida.
Alguns exemplos:
Capítulo 8 – Arquitetura de processadores 8-25

 Multiplicacão de números inteiros de 16 bits


 Divisão de números inteiros de 32 bits
 Rotações com qualquer número de bits
 Movimentação e comparação de strings
 Instruções para manipulação direta de bits
 Instruções de repetição

Registradores BX, BP, SI e DI


Esses registradores permitem várias operações comuns em outros
registradores, e além delas, podem ser usados como índices. Por exemplo,
podemos usá-los para apontar posições de memíria usando expressões como:

[BX+valor] [BX+SI+valor]
[BP+valor] [BX+DI+valor]
[SI+valor] [BP+SI+valor]
[DI+valor] [BP+DI+valor]

Exemplos:

MOV BX,1000h ; Aponta para o endereço 1000h


MOV AL,[BX+15h] ; Lê para AL o valor que está em 1015h
MOV BX,2000h
MOV SI,100h
MOV AL,[BX+SI+20h] ; Lê para AL o valor que está em 2120h

O uso de índices torna a programação extremamente mais simples quando


temos que lidar com estruturas de dados na memória, como strings e
matrizes.

Registradores de segmento
O 8086 podia endereçar 1 MB de memória, muito mais que os 64 kB
permitidos pelo 8080. No 8080, toda a memória era tratada como uma única
coleção de bytes, contendo instruções, dados e stack. No 8086, esses
elementos também ficam nesta mesma memória, apesar de maior. Apesar da
memória ser homogênea do ponto de vista físico, seu uso é dividido em
áreas chamados segmentos. Instruções devem ficar no segmento de código,
dados devem ficar no segmento de dados ou no segmento extra, e a stack
deve ficar no segmento de stack. Para manter essas 4 áreas de memória
diferentes, o 8086 possui 4 registradores de segmento, que são:

CS: Code segment


8-26 Hardware Total
DS: Data segment
ES: Extra segment
SS: Stack segment

Cada segmento no 8086 é uma área de memória com 64 kB. Os


registradores de segmento indicam o endereço inicial dos respectivos
segmentos. Note que esses registradores têm 16 bits, enquanto os endereços
de memória do 8086 têm 20 bits. O processador obtém o endereço inicial de
cada segmento adicionando 4 bits zero (ou um dígito 0 hexadecimal) à
direita do valor existente no regitrador de segmento. Por exemplo, se o
registrador CS está com o valor 7BC3, então o segmento de dados começa
no endereço 7BC30.

Figura 8.8
Regitradores de segmento indicam os
inícios dos respectivos segmentos.

A figura 9 mostra como é feito o endereçamento da memória dentro de um


segmento. Todos os acessos a instruções são feitas automaticamente no
segmento de dados. Digamos que CS esteja armazenando o valor 2800h, o
que indica que o segmento de dados começa em 28000h. Digamos que o
registrador IP (Instriction Pointer) esteja com o valor 0153h. Para obter o
endereço de memória, fazemos a seguinte conta: adicionar um zero à direita
do valor do segmento e somar este resultado com o offset, que no caso é o
valor de IP. Encontramos então 28000h+0153h=28153h.
Capítulo 8 – Arquitetura de processadores 8-27
Figura 8.9
Determinação de um endereço absoluto a
partir do segmento e do offset.

Todos os endereços do 8080 são compostos desta forma. O endereço usado


para acessar a memória (de 00000 a FFFFF) é o que chamamos de endereço
absoluto. O endereço absoluto sempre é formado por um valor de segmento
e um offset. O valor do segmento é adicionado de um zero hexadecimal à
sua direita e somado com o valor do offset, resultando no endereço absoluto.

Vejamos um outro exemplo. Digamos que tenhamos DS=8A9Fh e


BX=7CB6h. A instrução MOV AL,[BX] buscará um byte do endereço
absoluto dado por:

8A9F0h +7CB6h = 926A6h

Observe que cada posição de memória pode ser endereçada de várias outras
formas. Por exemplo, o mesmo endereço absoluto 926A6H pode ser obtido
fazendo DS=9000h e BX=26A6h.

Todas as instruções a serem executadas são buscadas no segmento de


código, portanto o registrador CS é usado na determinação do endereço
absoluto. Todos os acessos a dados são feitos no segmento de dados,
portanto o processador usa o valor de DS no cálculo do endereço absoluto.
Certas instruções que manipulam strings utilizam o segmento extra (ES é a
base para o cálculo), e as operações com a stack são feitas no segmento de
stack, determinado por SS.

Usando 4 segmentos de 64kB (código, dados, stack e extra), somos levados a


concluir erradamente que um programa de 8086 pode ter no máximo 64 kB.
Na prática não é isso o que ocorre. Para programas pequenos, não é
necessário usar integralmente os 64 kB de cada segmento, portanto pode
8-28 Hardware Total
ocorrer interseção entre os segmentos. Além disso, instruções especiais
alteram automaticamente o valor de CS em operações de desvio e chamadas
de rotinas, resultando em programas de maior tamanho, podendo até mesmo
usar toda a memória disponível. Um mesmo programa pode ter múltiplos
segmentos de código e de dados, manipulando assim quantidades maiores
de memória.

Modos de endereçamento
O 8086 possui vários modos de endereçamento:

Imediato: Opera com valores constantes. Exemplos:

MOV AX,0 ; Carrega AX com 0


MOV BX,1000h ; Carrega BX com 1000h
MOV DL,20h ; Carrega DL com 20h
MOV SI,3500h ; Carrega SI com 3500h

Registrador: Quando envolve apenas registradores. Exemplos:

MOV AX,BX ; Copia BX em AX


MOV CX,SI ; Copia SI em CX
MOV DS,AX ; Copia AX em DS
OR BX,CX ; Faz um “OR” de BX com CX. Resultado fica em BX

Direto: Qundo faz referência a um endereço fixo de memória. Exemplos:

MOV AX,[1000h] ; Carrega o valor do endereço 1000h em AL e do endereço 1001h em AH


ADD CX,[2000h] ; Carrega o valor de 16 bits dos endereços 2000h/2001h em CX
CMP SI,[1020h] ; Carrega o valor de 16 bits dos endereços 1020h/1021h em SI

Indexado: Este é o modo de endereçamento mais flexível. Usa os


registradores BX, BP, SI e DI como índices. Os índices podem ser usados
sozinhos ou combinados, ou seja, o valor da soma de BX ou BP com SI ou
DI. Sobre este valor ainda pode ser adicionada uma constante. Exemplos:

MOV CL,[BX]
MOV DL,[BP]
MOV AX,[SI]
MOV AH,[DI]
MOV CX,[BX+5]
MOV DL,[BP+50]
MOV AL,[SI+100]
MOV AX,[DI+1200]
MOV AX,[BX+SI]
MOV CL,[BX+SI+200]
MOV AH,[BP+DI]
MOV DX,[BP+DI+300]
MOV CX,[DI+4800]
Capítulo 8 – Arquitetura de processadores 8-29
MOV DX,[BP+SI]
MOV AH,[BP+SI+2000]
MOV AL,[BP+DI]
MOV DX,[BP+DI+700]

Note que não é permitido usar em uma única instrução, dois


endereçamentos à memória. Por exemplo, não podemos usar MOV [SI],[DI].
Apesar disso podemos mover dados entre quaisquer resitradores e quaisquer
formas de endereçamento da memória (coisa que não era permitida no
8080). No caso do 8086, existem algumas raras exceções. Por exemplo, não
podemos usar livremente os registradores de segmento com todas as
operações que são suportadas pelos demais registradores. Não podemos
usar, por exemplo, ADD DS,AX. Os registradores de segmento permitem
apenas instruções de movimentação de dados.

Instruções de movimentação de dados


MOV: Move dados entre dois locais quaisquer. Podem ser usados nesta
instrução, qualquer um dos modos de endereçamento já citados. Exemplos:

MOV AX,BX
MOV DI,1000h
MOV [BX+SI],20
MOV CL,19
MOV SI,[BX]
MOV [BP+DI],CX

Note que o 8086 não tem instruções equivalentes a STAX e LDAX do 8080,
que usam pares BC e DE para indexar a memória, já que não existem os
modos de endreçamento [CX] e [DX].

XCHG: No 8080 esta instrução permutava os valores de DE e HL. No 8086,


quaisquer valores podem ser permutados, o que engloba todos os
registradores e a memória, endereçada por todos os modos válidos. É
permitido inclusive usá-la com elementos de 8 bits. Exemplos:

XCHG BX,DX
XCHG AX,SI
XCHG AL,BH
XCHG CX,[BX+SI]

XLAT: Esta é uma instrução bastante especializada. É útil para implementar


traduções de códigos. Digamos que tenhamos na memória uma tabela de
256 valores, e queremos obter o valor desta tabela, cujo índice está em AL.
A instrução XLAT faz isso, uma operação equivalente a MOV AL,[BX+AL].
8-30 Hardware Total
Instruções aritméticas
NEG: Inverte o sinal do valor aritmético especificado. Se o número for
positivo, tornar-se-a negativo, e vice-versa. Note que números negativos
necessitam de um bit (o mais significativo) para indicar o sinal, e os demais
para indicar a magnitude. Números com sinal armazenados em 8 bits podem
portanto variar entre –128 e +127. Com 16 bits, variam entre –32.768 e
+32.767.

NEG AL
NEG AX
NEG BX
NEG DX
NEG byte ptr [BX+SI]
NEG word ptr [DI+4]

Estamos apresentando agora os prefixos byte ptr e word ptr. Esses prefixos
são utilizados para informar ao assembler a real intenção do programador, se
é acessar um dado de 8 ou de 16 bits. Por exemplo, na instrução MOV AL,
[BX], o assembler sabe que o valor da memória a ser acessado é de 8 bits, já
que o outro operando é AL, que é também de 8 bits. Já em instruções como
NEG [BX], o assembler não saberia se a operação deve ser feita sobre o byte
cujo endereço é dado por BX, ou se deve operar sobre os dois bytes (word)
com este endereço. Usamos então os prefixos byte ptr e word ptr quando
necessário para dar esta informação ao assembler.

ADD, ADC: Soma os dois operandos. O resultado é colocado no primeiro


operando. A operação pode ser feita com 8 ou 16 bits, dependendo do
operando. A instrução ADC soma os dois valores com o bit Carry, o que é
usado para fazer o “vai 1”, agrupando dados de 16 bits para formar dados
com maior número de bits. Exemplos:

ADD BX,SI
ADD AX,[BX+DI]
ADD CL,AH
ADD DX,CX
ADD [SI],DX
ADC CX,[BX+SI]
ADC AH,[BP+SI+3]
ADC DX,BX
ADC [SI],AX

SUB, SBB: Essas duas instruções utilizam os mesmos operandos das


instruções ADD e ADC. Fazem a subtração de valores. A diferença entre
elas é que a SBB subtrai também o valor do bit Carry, tornando possível a
operação de “pedir emprestado”, o que é necessário para agrupar vários
Capítulo 8 – Arquitetura de processadores 8-31
dados de 16 bits, manipulando assim números inteiros com maior número de
bits. Exemplos:

SUB BX,DX
SUB CX,[BP+DI]
SUB CH,DL
SUB CX,AX
SUB [SI],BX
SBB AX,[BX+DI]
SBB CX,[BP+SI+3]
SBB CX,AX
SBB [SI],CX

MUL, IMUL: São as duas instruções de multiplicação do 8086. Ambas


podem operar com 8 e 16 bits. A diferença é que MUL é usada para
números sem sinal, somente positivos, enquanto IMUL aceita números
inteiros, sejam positivos ou negativos. Nesta multiplicação, um dos fatores é
sempre AX ou AL. O outro fator pode ser qualquer operando na memória
ou um outro registrador, com 8 ou 16 bits. Ao multiplicarmos dois valores de
8 bits, o resultado é armazenado nos 16 bits de AX. Ao multiplicarmos dois
valores de 16 bits, o resultado é armazenado em 32 bits, ficando os 16 menos
significativos em AX e os 16 mais significativo em DX. Exemplos:

MUL CL
MUL BX
MUL byte ptr [SI]
IMUL DX
IMUL BX
IMUL CL

DIV, IDIV: São as instruções de divisão. O dividendo pode ser de 16 ou 32


bits. Se for de 16 bits, é usado o valor de AX. Se for de 32 bits, é usado o
valor obtido em DX e AX. O que definirá se o dividendo será de 16 ou 32
bits é o divisor. Se o divisor for de 8 bits, será considerado como dividendo,
AX, o quociente ficará em AL e o resto em AH. Se o divisor for de 16 bits,
será considerado como dividendo o número de 32 bits formado por DX e
AX. O quociente ficará em AX e o resto em DX.

Note que esta instrução parte do princípio de que o resultado “caberá” no


registrador destinado ao quociente. Se não couber, ocorrerá um erro
chamado “estouro de divisão” (divide overflow). Por exemplo, ao fazer a
conta 8000h dividido por 2, usando um divisor de 8 bits, o resultado será
4000h, que não cabe em 8 bits. Para não passar por este problema é melhor
fazer a mesma conta usando o divisor 2 com 16 bits (MOV CX,2 / DIV CX).
Assim como ocorre nas instruções MUL e IMUL, a instrução DIV opera
8-32 Hardware Total
apenas com números inteiros positivos, e a IDIV opera tanto com positivos
quanto com negativos.

INC, DEC: Incrementa de uma unidade e decrementa de uma unidade. Os


bits Carry e Zero são afetados por essas operações, portanto podem ser
usadas para implementar contadores. Por exemplo, para preencher a tela de
um terminal de vídeo com 2000 caracteres em branco, podemos usar o
seguinte trecho de programa:

MOV DX,2000 ; Número de bytes a serem enviados


ENVIA: MOV AL, 20h ; 20h é o código do caracter “espaço”.
CALL OUTCHAR ; Envia o caracter para o terminal de video
DEC DX ; Decrementa o contador
JNZ ENVIA ; Pula se não chegou a zero

Além de implementar contadores, as instruções INC e DEC também podem


ser usadas para implementar ponteiros para posições de memória, o que e
útil quando queremos manipular dados seqüenciais.

Instruções lógicas
NOT: Inverte todos os bits do dado especificado. Cada bit 1 se transforma
em 0, e cada bit 0 se transforma em 1. Exemplos:

NEG AX
NEG SI
NEG DL
NEG byte ptr [BX]
NEG word ptr [BP+DI]
NEG byte ptr [1000h]

AND, OR, XOR: São os tradicionais operadores lógicos “E”, “OU” e “OU
Exclusivo”. Não há necessidade de apresentar novamente a tabela verdade
desses operadores, já mostradas quando apresentamos as instruções do 8080.
A diferença aqui é que essas operações podem ser feitas com 8 ou 16 bits, e
os dois operandos podem ser quaisquer, desde que ambos sejam do mesmo
tipo (ou ambos são byte, ou ambos são word). O resultado da operação
ficará armazenado no primeiro operando.

AND AX,SI
AND CX,[BX+DI]
AND DL,CH
OR [SI],AL
OR AX,1040h
OR byte ptr[SI],20h
XOR BX,DX
XOR [SI+2],AL
XOR AL,AH
Capítulo 8 – Arquitetura de processadores 8-33

Shifts e Rotates
O 8086 tem diversas instruções para deslocar bits para a esquerda e para a
direita. São chamadas de shifts e rotates. As instruções SHL e SHR são
exemplos de shifts. Provocam o deslocamento de todos os bits para a
esquerda e para a direita, respectivamente. Bits 0 são introduzidos à direita e
à esquerda. A operação dessas duas instruções é mostrada na figura 10. Note
que no 8086, qualquer registrador ou posição de memória pode ser usada
com esta instrução. Podemos aplicar um deslocamento de um só bit ou de
múltiplos bits, como mostraremos mais adiante.

Figura 8.10
Instruções SHL e SHR.

A diferença entre um shift e um rotate é que o shift introduz um bit 0 ou 1


no no bit mais ou no bit menos significativo, como é o caso das instruções
SHL e SHR mostradas na figura 10. Uma instrução de rotate forma uma
espécie de “anel”, na qual o bit que sai em uma extremidade é recolocado
na otura extremidade. A figura 11 mostra as instruções ROL e ROR (rotate
left e rotate right). O exemplo da figura mostra a rotação de valores de 8 bits,
mas também podem ser usados operandos de 16 bits. Note que na instrução
ROL, o bit 7 é realimentado no lugar do bit 0. Na instrução ROR o bit 0 é
realimentado no lugar do bit 7. Em ambas as instruções, o bit que é
realimentado também é copiado no Carry. Este método de rotação é o
mesmo das instruções RLC e RRC do 8080.

Figura 8.11
Instruções ROL e ROR.
8-34 Hardware Total
As instruções RCL e RCR operam de forma similar, exceto pelo fato do bit
Carry fazer parte da rotação, ao invés de simplesmente ficar com uma cópia
do bit realimentado. A figura 12 mostra o funcionamento dessas duas
instruções, que são exemplos de rotates. Este é o mesmo método de rotação
usado pelas instruções RAL e RAR do 8080.

Figura 8.12
Instruções RCL e RCR.

Finalmente apresentamos as instruções SAL e SAR, que também são shifts,


da mesma forma como as instruções SHL e SHR já apresentadas. Note que
deslocar os bits uma posição para a esquerda, introduzindo zeros, equivale a
multiplicar o valor por 2, e deslocar os bits uma posição para a direita
equivale a dividir o valor por 2. Isto funciona para números positivos, mas
quando os números podem ter sinal (o sinal é representado pelo bit mais
significativo; 1 significa negativo e 0 significa positivo), é preciso que as
instruções de shift preservem este sinal. Para isso servem as instruções SAL e
SAR, que são chamados shifts aritméticos (assim como SHR e SHL são
chamados shifts lógicos). O funcionamento dessas duas instruções é
mostrado na figura 13.

Figura 8.13
Instruções SAL e SAR.

A instrução SAL é idêntica à instrução SHL, com a introdução de zeros. Já a


instrução SAR tem uma diferença. Ao invés de serem introduzidos zeros no
bit mais significativo, este é realimentado em si próprio, ou seja, é copiado
para o bit seguinte mas o seu próprio valor permanece inalterado. Esta
Capítulo 8 – Arquitetura de processadores 8-35
alteração permite que números negativos continuem negativos ao serem
deslocados para a direita (ou seja, divididos por 2).

Além da maior variedade de instruções de shifts e rotates, o 8086 permite


operar não apenas com o acumulador, mas com qualquer outro registrador
(exceto registradores de segmento), de 8 ou 16 bits. Também pode operar
com posições de memória, de 8 ou 16 bits. Uma outra diferença importante
é que o deslocamento pode ser feito apenas uma posição (como
exemplificado nas figuras), ou com múltiplas posições. Por exemplo,
podemos deslocar um valor 3 bits para a esquerda, o que equivale a usar 3
vezes consecutivas a mesma instrução. Para aplicar shifts e rotates múltiplos,
basta carregar no registrador CL, o número de vezes que os bits devem ser
deslocados. Exemplos:

SHR AX,1 ; Aplica um shift para a esquerda em AX, de 1 bit.


MOV CL,4 ; Prepara CL com o número de bits a serem deslocados
ROR BX,CL ; Roda BX 4 bits para a direita
SHL DL,1 ; Aplica um shift em DL de 1 posição para a esquerda

Desvios
As instruções de CALL, RET e JMP presentes no 8080 também estão
presentes no 8086. Também temos as formas condicionais da instrução JMP,
mas não temos formas condicionais das instruções CALL e RET. Por
exemplo, não existe a instrução RC (Return if Carry), como no 8080. No seu
lugar temos que fazer uma combinação das instruções JNC e RET.

As formas condicionais da instrução JMP estão representadas na tabela que


se segue:
8-36 Hardware Total

Note que muitas instruções possuem aliases, ou seja, sinônimos. Por


exemplo, “pule se menor ou igual” é a mesma coisa que “pule se não é
maior”, portanto existem duas instruções idênticas: JBE e JNA (jump if below
or equal / jump if not above).

Uma outra instrução interessante é LOOP. Esta instrução faz o seguinte:


decrementa o valor de CX, e se este registrador não chegou a zero, faz o
desvio para um label especificado. Por exemplo:

MOV CX,10 ; Contador para 10 vezes


MOV SI,1000 ; SI aponta para endereço 1000 da memória
MOV DI,2000 ; DI aponta para 2000
TRANSF: MOV AL,[SI] ; Pega um byte da origem
MOV [DI],AL ; Guarda no destino
INC SI ; Incrementa ponteiros
INC DI
LOOP TRANSF ; Decrementa CX e se não chegou a zero vai para TRANSF

Neste pequeno trecho de programa as 4 instruções MOV AL,[SI] / MOV


[DI],AL / INC SI / INC DI será executado 10 vezes, que é o valor inicial do
contador CX. Observe que este exemplo é meramente explicativo, já que
existe uma única instrução que faz tudo isso sozinha (REP MOVS), como
veremos mais adiante. O objetivo deste exemplo foi mostrar como a
instrução LOOP pode ser usada para implementar repetições.

Existem ainda as formas condicionais da instrução LOOP, que são LOOPE e


LOOPNE (ou LOOPZ e LOOPNZ). Essas instruções fazem previamente um
Capítulo 8 – Arquitetura de processadores 8-37
teste no bit Zero, e executação uma instrução LOOP caso a condição seja
satisfeita. Se a condição não for satisfeita, o loop será terminado. Podemos
usar os loops condicionais para fazer uma comparação ou finalizar uma
contagem antes imediatamente antes do final do loop, permitindo assim que
o loop possa ser finalizado mesmo que o contador não tenha chegado a zero.

Existe ainda a instrução JCXZ (jump if CX=0). Como o nome já diz, esta
instrução executa um desvio caso o valor de CX tenha atingido o valor zero.
Note que esta instrução, a instrução LOOP e suas formas condicionais, e as
instruções de shifts e rotates que podem usar em CL o número de bits a
serem deslocados, dão ao registrador CX uma espécie de “personalidade”.
Este registrador é usado como contador em todas essas instruções citadas, e
em outras que ainda vamos apresentar.

Rotinas e retornos
Como já abordamos, as instruções de chamadas e retornos de rotinas são
CALL e RET, e não possuem formas condicionais. Existem entretanto outras
instruções de chamadas e retornos.

A instrução INT é uma espécie de “interrupção de software”. Normalmente


é usada para serviços do sistema operacional. Os primeiros 1024 bytes da
memória são reservados para uma área chamada vetor de interrupções. Este
vetor tem 256 elementos, e cada um desses elementos é composto de 4
bytes, sendo 2 para indicar um segmento e 2 para indicar um offset. Cada
um desses 256 elementos é o endereço de uma função do sistema
operacional encarregada de um determinado serviço. Cabe ao produtor do
sistema operacional estipular como essas 256 interrupções serão usadas. Por
exemplo, no MS-DOS, a instrução INT 21h é usada para várias chamadas de
funções básicas de acesso a disco e E/S em geral.

Quando uma instrução CALL é executada, o endereço de IP é armazenado


na stack. A operação inversa é feita pela instrução RET. Quando uma
instrução INT é executada, os valores de CS e IP são armazenados na stack,
já que serão carregados com novos valores encontrados no vetor de
interrupções. O final de uma rotina de interrupção, seja ela de software ou
de hardware, tem que terminar com uma instrução IRET. A diferença é que
IRET obtém da stack, valores de CS e IP, enquanto uma instrução RET
comum obtém apenas o valor de IP.

Manipulação da stack
8-38 Hardware Total
As instruções PUSH e POP são utilizadas respectivamente para armazenar e
recuperar valores de 16 bits na pilha. Todos os registradores de 16 bits
podem ser usados com essas instruções, bem como dados de 16 bits da
memória. As instruções PUSHF e POPF são usadas para salvar e recuperar o
registrador de flags. Exemplos:

PUSH BX
PUSH SI
PUSH BP
PUSH DS
POP AX
POP CX
POPF

Interrupções e E/S
Várias instruções são usadas para o processador interagir com o hardware.
As instruções STI e CLI são hadas para habilitar e desabilitar interrupções.
Instruções IN e OUT fazem operações de entrada e saída com 8 ou 16 bits.
Nas instruções de 8 bits é usado o registrador AL, e nas instruções de 16 bits
é usado o registrador AX. Exemplos:

IN AL,80h ; Lê porta de 8 bits no endereço 80h


IN AX,60h ; Lê porta de 16 bits no endereço 60h
OUT 43h,AL ; Envia dado de AL para a porta 43h
OUT 40h,AX ; Envia AL para a porta 40h e AH para a porta 41h

Usadas neste modo, as instruções IN e OUT permitem endereçar portas na


faixa de endereços de 00 a FF. Para endereçar portas em todo o espaço de
endereçamento do processador (0000 a FFFF) é preciso colocar em DX o
endereço da porta a ser acessada. Exemplos:

MOV DX,3F0h ; DX aponta para a porta 3F0


IN AL,DX ; Lê o valor da porta 3F0
MOV DX,278h ; Aponta para a porta 278h
OUT DX,AL ; Envia dado de AL para a porta 278h

Manipulação de strings
O processador 8086 e seus sucessores têm a capacidade de manipular strings,
que são cadeias de caracteres. Essas funções são importantes em
processadores de texto e compiladores. Em todas as instruções de strings, os
registradores SI e DI apontam para as strings envolvidas. SI aponta para a
origem, localizada no segmento de dados. DI aponta para a string destino,
localizada no segmento Extra. Portanto as strings de origem e destino estão
em CS:SI e ES:DI, respectivamente. O registrador CX é usado como
Capítulo 8 – Arquitetura de processadores 8-39
contador, e AL ou AX são usados para manter o dado nas operações de
busca, leitura e escrita.

MOVSB e MOVSW – Move um dado (8 ou 16 bits) da origem para o


destino. MOVSB opera com bytes, e é equivalente à seguinte seqüência:

MOV dado8,DS:[SI]
MOV ES:[DI],dado8
INC SI
INC DI

MOVSW opera com dados de 16 bits, e é equivalente à seqüência

MOV dado16,DS:[SI]
MOV ES:[DI],dado16
ADD SI,2
ADD DI,2

Se quisermos que seja movido um certo número de bytes, podemos usar um


contador e decrementá-lo após a instrução MOVSB ou MOVSW, e voltar à
instrução se o contador não chegou a zero. Podemos usar a instrução LOOP
para este fim, que decrementa CX e faz o desvio caso não tenha chegado a
zero. Melhor ainda, podemos usar o prefixo REP antes da instrução. Para
usar este prefixo, carregamos em CX o número de vezes que a instrução
deve ser executada. Usamos então essas instruções nas formas:

REP MOVSB
REP MOVSW

Observe que o prefixo REP faz com a que a instrução seguinte seja
executada CX vezes, mas este prefixo só pode ser usado em operações com
strings.

Outras operações com strings são:

LODSB e LODSW: Carrega em AL ou AX o dado apontado por DS:SI. O


registrador SI é incrementado de 1 ou 2 unidades dependendo de ser a
operação de 8 ou 16 bits.

STOSB e STOSW: Armazena AL ou AX em ES:DI. O registrador DI é


incrementado de 1 ou 2 unidades para operações de 8 ou 16 bits,
respectivamente. Aliado ao prefixo REP, essas instruções permitem
preencher uma área de memória com um valor constante.
8-40 Hardware Total
SCASB e SCASW: Compara AL ou AX com o valor da memória apontado
por ES:DI. O registrador DI é somado com 1 ou 2 para operações de 8 ou
16 bits. O flag Zero é ligado de acordo com o resultado da comparação,
portanto logo após uma instrução SCASB ou SCASW podemos usar um
desvio condicional para indicar se o valor de AL ou AX foi “encontrado” ou
não na memória.

CMPSB e CMPWS: Compara os dados apontados por DS:SI e ES:DI. Os


flags são ligados de acordo com a comparação, portanto podemos usar
depois dessas instruções, um desvio condicional. Os ponteiros SI e DI são
atualizados. Essas instruções permitem achar uma string dentro de uma área
de memória. É o que ocorre quando usamos em um processador de textos, o
comando Localizar.

Outras instruções
O 8086 tem muitas outras instruções. Optamos por não apresentar todas aqui
para não tornar o capítulo muito complexo. Você pode obter no site da Intel
(www.intel.com), o manual completo da linguagem assembly dos
processadores modernos. A diferença é que existem novas instruções,
sobretudo aquelas para manipular valores de 32 bits. Mesmo não sendo
totalmente completa, esta apresentação resumida atendeu ao nosso objetivo
de mostrar como o processador opera internamente e como os programas
são executados.

Arquitetura do 80286
O 80286 também é um processador de 16 bits. Possui os mesmos
registradores internos existentes no 8086. Entretanto possui algumas novas
instruções, bem como um novo modo de endereçamento capaz de operar
com 16 MB de memória, o que era uma quantidade espantosa para a época
do seu lançamento (1982), quando a maioria dos computadores tinha 64 kB
de memória. O 80286 podia operar em duas modalidades. O chamado
modo real (8086 real address mode) permite endereçar até 1 MB de
memória. Nesse caso o processador comporta-se como um 8086, apenas
acrescido de algumas novas instruções. Para uso em sistemas operacionais
mais avançados, o 80286 podia operar no modo protegido (protected virtual
address mode). Neste modo, o processador pode operar com 16 MB de
memória física e até 1 GB de memória virtual por tarefa.

Multitarefa
O 80286 foi criado visando facilitar a multiprogramação ou multitarefa, na
qual vários programas podem ser executados “simultaneamente”. O que
Capítulo 8 – Arquitetura de processadores 8-41
ocorre é uma divisão do tempo entre os vários processos que estão sendo
executados. Uma forma simples de dividir o tempo é alocar períodos iguais
(10 milésismos de segundo, por exemplo), e distrubuir esses períodos entre
os processos. Quando um processo começa a ser executado, será
interrompido 10 ms depois, e o sistema operacional deve fazer com que o
processador dê atenção ao processo seguinte. Desta forma usando um
esquema de “rodízio”, todos os processos são executados ao mesmo tempo,
porém em cada instante um só está efetivamente em execução, e os demais
estão aguardando. O período no qual o processador está dedicado a um
processo é chamado time slice.

Existem outros esquemas mais sofisticados para implementar a multitarefa.


Processos podem ter prioridades diferentes e time slices diferentes, visando
aumentar a eficiência. Um processo que faz muitas operações de E/S tende a
não utilizar integralmente seu time slice, já que freqüentemente precisa parar
e aguardar pela operação de E/S (a leitura de um arquivo do disco, por
exemplo). Este tipo de processo é classificado como “I/O bounded”. Por
outro lado, processos que fazem poucas operações de E/S e realizam muito
processamento são chamados de “CPU bounded”. Esses processos tendem a
utilizar integralmente o seu time slice. Visando aumentar a eficiênica, o
sistema operacional pode reduzir o time slice e aumentar a prioridade para
os processos “I/O bounded”. Pode ainda aumentar o time slice e reduzir a
prioridade para os processos “CPU bounded”. Enfim, o sistema operacional
pode alterar as prioridades e a duração do time slice para que o trabalho do
processador seja distribuído uniformemente entre os vários processos.

Novas instruções do 80286


As novas instruções incorporadas a este processador podem, em sua maioria,
ser utilizadas tanto no modo real como no modo protegido. Apenas como
referência rápida, citaremos algumas delas:

PUSHA e POPA: Realizam operações de PUSH e POP com todos os


registradores do processador. Essas instruções tornam mais rápida a
operação de salva e restauração de contexto, necessária nas entradas e saídas
de rotinas e nas mudanças entre uma tarefa e outra.

IMUL: No 8086 esta instrução fazia a multiplicação de AL ou AX por um


registrador de 8 ou 16 bits. No 80286, esta instrução também pode operar
com valores constantes. Por exemplo, se quisermos multiplicar AX por 38,
basta usar IMUL AX,38. No 8086 era preciso usar algo como MOV CX,38 /
IMUL CX.
8-42 Hardware Total
Shifts e Rotates: No 8086 essas operações eram feitas com 1 bit ou com
múltiplos bits, mas era preciso carregar em CL o número de bits a serem
deslocados. No 80286 o valor pode ser usado diretamente na instrução. Por
exemplo: SHR AX,3

INSB, INSW, OUTSB, OUTSW: São versões mais avançadas das


instruções IN e OUT. Agora essas instruções operam com strings. Portanto é
possível, por exemplo, enviar para um endereço de E/S uma seqüência de
dados da memória. O prefixo REP e o contador CX podem ser usados para
especificar o número de transferências a serem realizadas.

ENTER e LEAVE: Essas novas instruções são usadas para implementar


rotinas em linguagens de alto nível. A instrução ENTER cria o que
chamamos de stack frame, no qual ficam armazenados parâmetros e
variáveis locais da rotina. A instrução LEAVE realiza o proceso inverso.
Essas duas novas instruções tornaram o uso de rotinas em assembly mais
adequado ao método usado pelas linguagens de alto nível, além de permitir
uma entrada e saída mais rápida das rotinas.

BOUND: Essa instrução checa se o índice de um array está entre os limites


máximo e mínimo permitidos pelo array. O array nada mais é que um vetor
na memória. Por exemplo, se temos um array A com índices de 0 a 10, seus
elementos são A[0], A[1], A[2], .... , A[10]. Se tentarmos usar uma expressão
como A[30], um programa em linguagem de alto nível deverá ser suspenso
com a apresentação da mensagem de erro “Invalid index”. Certas linguagens
não testam índices inválidos e cabe ao programador garantir que o índice é
válido. Outras linguagens monitoram os índices durante a execução, mas isto
resulta em mais tempo gasto. A instrução BOUND permite fazer a checagem
dos índices em tempo de execução, de forma mais rápida.

Instruções para o modo virtual: O 80286 possui várias instruções que


servem para que o sistema operacional faça o gerenciamento da memória e
das tarefas quando opera em modo protegido.

Modo real
Visando manter compatibilidade com os programas escritos para 8086/8088,
o 80286 é capaz de operar no chamado modo real. O processador passa a se
comportar como um 8086, endereçando apenas 1 MB de memória. É apenas
acrescido das novas instruções adicionadas ao conjunto de instruções
originais do 8086, exceto aquelas usadas para gerenciamento de tarefas. Por
isso é quase certo dizer que o 80286 operando em modo real é equivalente
ao 8086. Alguns dizem que isso é “o mesmo que um XT”. É mais ou menos
Capítulo 8 – Arquitetura de processadores 8-43
isso o que acontece, mas devemos lembrar que o 8086, 8088 e 80286 são
processadores, e o XT é um computador. Seria correto dizer que no modo
real, o 80286 opera como um 8086 acrescido de instruções novas como
BOUND, ENTER, LEAVE, INSB, INSW, OUTSB, OUTSW, novos shifts,
rotates e a nova instrução IMUL, além das instruções PUSHA e POPA.
Entretanto muitos programadores optavam por não utilizar essas novas
instruções, para que seus programas fossem compatíveis com o IBM XT.
Alguns programas tinham versões para XT e versões otimizadas para 80286,
que usavam essas novas instruções. Apenas no final dos anos 80, quando o
AT-286 era mais comum que o XT, surgiram programas que rodavam
somente no 286, utilizando essas novas instruções.

Modo protegido
O 80286 passa a ter novos recursos para gerenciamento de tarefas e
endereçamento de memória quando opera no modo protegido. Lembre que
no 8086, cada segmento tinha 64 kB, e era definido pelos registradores de
segmento (CS, DS, ES e SS). Todos os segmentos eram contidos dentro da
memória física de 1 MB. No 286 operando em modo protegido, os
segmentos também têm 64 kB, e são definidos por um registrador de
segmento (CS, DS, ES e SS) e um offset. A diferença está na formação desses
endereços. Consideremos por exemplo o endereço F000:1000 no modo real.
Conforme mostramos neste capítulo, o endereço absoluto correspondente é
F1000. É obtido acrescentando um zero hexadecimal (ou 4 zeros binários) à
direita do segmento e somando o resultado com o offset. O resultado terá 20
bits, permitindo endereçar até 1 MB.

No modo protegido, os endereços também são indicados por um valor de


segmento e um valor de offset. A diferença é que o valor do segmento não é
usado diretamente na formação do endereço. Ele é usado como índice em
uma tabela chamada segment descriptor table. A partir do valor do
segmento é determinado o elemento da tabela que traz o endereço real do
segmento desejado, com 24 bits. Este valor é somado com o offset,
resultando em um endereço físico de 24 bits, permitindo assim endereçar até
16 MB de memória.

As tarefas (tasks) no 286 recebem um identificador de privilégio que varia de


0 a 3. O privilégio 0 é dado ao núcleo do sistema operacional. É o único
nível que permite gerenciar parâmetros das demas tarefas, tendo acesso a
todas as instruções de gerenciamento de memória e de tarefas. Os níveis de
privilégio 1 e 2 são usados pelo sistema operacional, e o nível 3 é dado às
aplicações. Isso impede que um programa de um usuário possa interferir
8-44 Hardware Total
com o gerenciamento de memória e de tarefas. Note que esses recursos só
estão disponíveis no modo protegido.

O pouco uso do modo protegido do 286


Apesar do 286 ter sido lançado em 1982 e bastante avançado para a sua
época, quase sempre este processador era usado no modo real. Este
processador começou a ser desenvolvido no final dos anos 70, e até o seu
lançamento, ocorreu uma verdadeira reviravolta na indústria de
microcomputadores:

a) Transição entre os micros de 8 e de 16 bits


b) Lançamento do IBM PC
c) Lançamento do MS-DOS
d) Consolidação do IBM PC e do MS-DOS no mercado de micros

O modo real é bastante limitado. Lembra muito a operação dos


processadores de 8 bits. Já o modo protegido tem características de
computadores mais poderosos. Recursos antes encontrados apenas em
computadores de grande porte passariam a fazer parte dos
microcomputadores. Era tido como óbvia a criação de novos sistemas
operacionais mais avançados, operando em modo protegido. Tudo indicava
que esses novos sistemas operacionais tomariam o lugar do arcaico DOS de
modo real, que não passava de uma herança do sistema operacional CPM,
usado nos micros de 8 bits desde os anos 70. Seriam criados sistemas mais
poderosos, e o 80286 era um processador com recursos avançados para
atender a esses novos sistemas.

Não foi bem isso o que ocorreu. O IBM PC passou a ser cada vez mais
usado, até chegar ao ponto em que microcomputador passou a ser sinônimo
de IBM PC. O número de PCs aumentou ainda mais depois que surgiram os
clones, ou seja, PCs similares produzidos por outros fabricantes. Ao lado do
PC, o sistema operacional MS-DOS com toda a sua limitação tornou-se mais
utilizado que todos os demais sistemas operacionais.

Quem poderia imagiar, durante o projeto do 286, que o arcaico MS-DOS


faria tanto sucesso durante tantos anos? Por isso o 286 não foi projetado sob
medida para o MS-DOS, e sim para sistemas mais avançados. Apesar de um
PC equipado com um 286 de 6 MHz ser quase 6 vezes mais veloz que um
XT, o MS-DOS não podia fazer uso dos 16 MB (memória estendida) de
memória que o 286 era capaz de endereçar. O 286 só oferece 16 MB no
modo protegido, e o MS-DOS só opera no modo real.
Capítulo 8 – Arquitetura de processadores 8-45
Visando vencer esta dificuldade, a Lotus, a Intel e a Microsoft criaram uma
especificação de memória chamada EMS (Expanded Memory Specification),
também conhecida como memória expandida. Consiste em uma placa de
memória com circuitos de controle que permitiam acessar maiores
quantidades de memória no modo real, usando um mecanismo de
chaveamento de bancos de memória. A memória expandida foi muito mais
usada que a estendida, que só estava disponível no modo protegido.

Placas de CPU 286 com suporte para mais de 1 MB de memória podiam


operar com memória estendida se fosse usado um sistema operacional de
modo protegido. Essas placas também possuíam circuitos de controle que
transformavam sua memória acima de 1 MB em memória EMS, permitindo
o seu acesso pelos programas do MS-DOS.

Um outro problema agravava a situação do 286. Uma vez entrando em


modo protegido, não poderia retornar ao modo real, a menos que o
processador sofresse um RESET. Uma análise precipitada poderia nos levar
a pensar que isso foi um erro de projeto da Intel. Lembre-se entretanto que o
286 foi lançado em 1982, e naquela época o IBM PC não era o computador
mais comum no mercado, nem o MS-DOS era o sistema operacional mais
usado, e a evolução da tecnologia apontava para o uso de novos sistemas
operacionais de modo protegido. Para que então um processador precisaria
ir e voltar entre o modo real e o modo protegido? Afinal não existiam
sistemas mistos. Ou eram de modo real (MS-DOS e CPM-86, por exemplo)
ou de modo protegido. A própria IBM, na época, havia comprado parte da
Intel, e nos seus planos estava o sistema operacional OS/2 (modo protegido)
que cairia como uma luva no 286.

O processador 80386, lançado em 1985, foi projetado dentro de uma


realidade em que o PC dominava o mercado, assim como o MS-DOS. Seu
projeto levou em conta esses fatores, e mudanças foram feitas nos seus
modos de endereçamento, como veremos a seguir.

Arquitetura do 80386
É muito importante conhecer bem o 386, pois todos os processadores
posteriores, do 486 ao Pentium 4, utilizam métodos semelhantes de
gerenciamento de memória e multitarefa, bem como possuem conjuntos de
instruções similares, com apenas algumas poucas diferenças.

A figura 14 mostra o diagrama interno do 386. Além de vários elementos


encontrados em outros processadores, destacam-se as unidades de
8-46 Hardware Total
gerenciamento de memória (segmentation unit e paging unit) e a unidade de
proteção (protection test unit), necessária ao funcionamento da multitarefa.

*** 100%
Figura
8.14
Diagrama interno do
processador 80386.

Registradores internos do 386


O 80386 é um processador de 32 bits. Para que fosse mantida a
compatibilidade com o 8086 e com o 80286, cada registrador de 32 bits pode
ser também acessado em modos de 16 e de 8 bits. Por exemplo, o
registrador EAX tem 32 bits, mas seus 16 bits menos significativos são o
próprio registrador AX do 8086 e 80286, bem como os 8 bits menos
significativos são o registrador AL.

Figura
8.15
Registradores do
80386.

Os registradores AX, BX, CX, DX, SI, DI, BP e SP foram todos expandidos
para 32 bits. Os registradores de segmento CS, SS, DS e ES também estão
presentes e são de 16 bits. Foram adicionados mais dois registradores para
apontar para segmentos de dados: FS e GS. O contador de instruções (IP),
Capítulo 8 – Arquitetura de processadores 8-47
que antes tinha 16 bits, agora tem 32 bits, e novos flags resultaram no
aumento do registrador de flags também para 32 bits.

Novas instruções do 80386


Somente pelo fato dos registradores serem agora de 32 bits, o 80386 ganhou
várias novas instruções. Todas as instruções que operavam com 8 e 16 bits,
agora operam também com 32 bits. Por exemplo, podemos manter números
de 32 bits nos registradores EAX e EBX e usar a instrução ADD EAX,EBX,
obtendo um resultado de 32 bits. No 8086 e no 80286, uma soma de 32 bits
tinha que ser feita em duas etapas.

Além da maioria das instruções antigas agora poderem também operar com
32 bits, existem várias instruções novas. A maioria delas são aplicadas ao
gerenciamento de tarefas e ao gerenciamento de memória. São portanto
instruções para serem usadas pelo sistema operacional, e não pelos
programas comuns.

Foram ainda criados novos modos de endereçamento mais poderosos.


Podemos por exemplo usar agora outros registrdores como EAX, EDX e
ECX para apontar a memória, coisa que não era permitida no 8086 nem no
80286. Podemos usar endereçamentos dados por expressões como
[EDX*8+EAX] e expressões similares, permitindo o endereçamento mais
rápido de matrizes, como por exemplo, uma área de memória de vídeo onde
está sendo desenhado um gráfico. Desta forma é possível manipular
estruturas de dados mais complexas utilizando um reduzido número de
instruções.

Existem ainda algumas novas instruções para manipulação de valores


binários, mas não apresentam grande impacto sobre o desempenho. O ponto
forte para os programas comuns é realmente o “upgrade” das antigas
instruções de 16 bits para 32 bits. Essas novas instruções de 32 bits podem
inclusive ser usadas (exceto as de endereçamento) quando o processador
opera no modo real, no qual seu comportamento é similar ao de um 8086.

Modo real
No modo real, o esquema de enereçamento do processador é similar ao do
8086. Além disso não estão disponíveis os recursos avançados de
endereçamento e gerenciamento de memória, nem de multitarefa. Estão
entretanto disponíveis as novas instruções que manipulam dados e 32 bits e
novos modos de endereçamento de memória.
8-48 Hardware Total
Os endereços no modo real são formados a partir de um segmento e offset,
ambos de 16 bits. O valor existente no registrador de segmento é adicionado
de 4 bits “0” e somado com o offset. O resultado é o endereço efetivo de 20
bits, usado para acessar a memória física. Neste modo é possível acessar até
1 MB de memória, e cada segmento tem 64 kB. Exatamente como no 8086.

Figura 8.16
Endereçamento no modo real.

Assim como ocorreu no 286, o modo real do 386 foi criado para oferecer
compatibilidade com os programas escritos para o 8086, como o DOS e seus
aplicativos. O modo real está presente também nos processadores modernos,
por isso é possível executar um boot com um disquete de DOS em um PC
equipado com o Pentium 4 e usar programas de modo real.

Modo protegido
Além da velocidade resultante do maior clock e de operar diretamente com
dados de 32 bits, o 80386 tem a grande vantagem de operar no modo
protegido, possibilitando acessar grandes áreas de memória e permitir a
multitarefa. Os valores de offset podem ter 16 ou 32 bits, permitindo formar
segmentos de 64 kB e de 4 GB. Graças ao endereçamento com offsets de 32
bits, um programa poderia teoricamente ocupar toda a memória física de um
processador 386.

O endereçamento com offsets de 32 bits é possível graças aos novos


registradores de 32 bits. Observe na figura 15 que todos os registradores
usados para endereçar a memória agora têm 32 bits: AX, BX, CX, SI, DI,
BP, SP e IP.
Capítulo 8 – Arquitetura de processadores 8-49
Figura 8.17
Endereçamento de memória no modo protegido.

A figura 17 mostra a formação de um endereço no modo protegido. Um


endereço de memória é formado a partir de um segmento e de um offset. O
valor do segmento (CS, DS, ES, SS, FS ou GS) tem 16 bits, mas ao contrário
do que ocorre no modo real, não é simplesmente adicionado de zeros e
somado com o offset. Este valor é usado como índice para uma tabela na
memória (segment descriptor table), na qual é obtido o endereço verdadeiro.
Este valor tem 32 bits e permite assim definir um segmento em qualquer
local da memória física de 4 GB. O endereço do início do segmento é
somado com o offset de 16 ou 32 bits, formando assim segmentos de 64 kB
ou de 4 GB.

Segmentação, paginação e memória virtual


Na verdade o mecanismo de endereçamento é ainda mais complexo. O
endereço de 32 bits usado para acessar a memória de 4 GB é chamado de
endereço linear. Uma vez formado, este endereço pode ser enviado
diretamente para a memória, permitindo o seu acesso no modo de
segmentação. Os segmentos de 32 bits não utilizam o valor máximo de 4 GB,
que seria muito grande. Mútiplos processos podem utilizar vários segmentos
diferentes, e na tabela de descritores de segmentos está indicado o tamanho
efetivamente usado para cada segmento. O valor de 4 GB é o limite máximo,
mas podemos ter segmentos com vários valores diferentes.

Um outro mecanismo de gerenciamento de memória mais simples é a


paginação. Explicando de forma simples, cada endereço físico gerado a
partir do segmento e offset (chamado de endereço linear) é usado como
índice para uma tabela de páginas. A partir desta tabela é obtido o endereço
físico na memória. Além de fazer a tradução entre endereço linear e
endereço físico, o mecanismo de paginação permite proteger cada página de
outros processos, evitanto violações de memória. Permite ainda implementar
a memória virtual, uma área do disco rígido na qual é simulada uma grande
quantidade de RAM.
8-50 Hardware Total
Figura 8.18
Paginação.

O 80386 opera com páginas de 4 kB. Todas essas páginas podem estar
fisicamente localizadas na memória RAM. Entretanto é comum utilizar este
recurso para implementar a memória virtual. No Windows a memória virtual
consiste em um arquivo mantido no disco (swap file ou arquivo de troca), o
WIN386.SWP, localizado em C:\Windows. A RAM é fisicamente dividida
em páginas de 4 kB, assim como o arquivo de troca, entretanto este arquivo
tem tamanho bem maior que a memória física.

Digamos que um PC tenha 64 MB de RAM e esteja configurado com uma


memória virtual de 256 MB. Usando um mecanismo de gerenciamento de
memória por paginação, tanto a memória física quanto a memória virtual
utilizarão páginas de 4 kB:

Memória física: 16.386 págingas de 4 kB: total = 64 MB


Memória virtual: 65.536 páginas de 4 kB: total = 256 MB

Os programas podem então acessar a memória como se existissem 256 MB


disponíveis. Em um dado instante, apenas as páginas que estão mapeadas na
RAM podem ser acessadas pelos programas. Quando um programa faz o
acesso a um endereço de memória cuja página não está mapeada, ocorre
automaticamente uma interrupção (page fault) de software chamada
“exceção restartável”. O atendimento desta interrupção é feito pelo núcleo
do sistema operacional, que providencia uma página livre na RAM e copia
para a mesma o conteúdo da página correspondente da memória virtual.
Quando não existe página disponível na RAM, o sistema operacional deve
transferir para o arquivo de troca, alguma página da RAM que esteja há mais
tempo sem ser usada. Terminada a liberação da página em RAM, o sistema
operacional providencia o retorno para a instrução que gerou a exceção.
Nete retorno, a instrução que tentou acessar a memória e não conseguiu é
repetida automaticamente, e desta vez poderá acessar a RAM.
Capítulo 8 – Arquitetura de processadores 8-51
Note que a segmentação e a paginação não estão necessariamente ligadas à
memória virtual. Um PC pode estar operando com a memória virtual
desativada e ainda assim ter o acesso à RAM feito por segmentação ou
paginação. Os descritores de segmento e de página oferecem métodos para
não apenas proteção de memória, mas também detectar quando uma área
não está mapeada na RAM, permitindo o acesso à memória virtual. Em
outras palavras, quando um PC tem muita memória RAM e desativamos a
memória virtual, o gerenciamento de memória do Windows continua sendo
feito através da paginação.

Translation Lookaside Buffer (TLB)


O acesso à memória através de paginação requer a conversão de um
endereço linear em endereço físico. A unidade de paginação precisa
descobrir qual é o endereço físico de cada página de 4 kB acessada. Esses
endereços são mantidos em uma tabela de páginas na memória, e a cada
acesso, o processador precisa descobrir através desta tabela, qual é o
endereço físico que deve ser acessado. Isto tornaria o processador lento, pois
qualquer acesso à memória precisaria de uma acesso prévio à tabela de
páginas. Para resolver o problema, o 386 tem uma pequena cache interna
dedicada a armazenar as informações sobre páginas recentemente acessadas.
Esta área é chamada de translate lookaside buffer (TLB). Este buffer é capaz
de armazenar a localização de 32 páginas, totalizando assim, 128 kB. É
estimado que em 98% dos acessos, o processador encontra as informações de
localização no TLB.

Modo virtual 8086


Este é um novo modo de operação introduzido no 386, compatível com o
modo real e com o modo protegido simultaneamente. Não é na verdade um
novo modo de operação, pois faz parte do modo protegido. Apenas as
tarefas designadas a operar neste modo têm atributos específicos que alteram
a formação dos endereços e o modo como os registradores de segmento são
tradatos.

Programas escritos para o modo real (exemplo: MS-DOS e seus aplicativos)


não funcionam no modo protegido. Basta lembrar que o sistema de
endereçamento é completamente diferente. Durante o projeto do 80386, o
IBM PC e o MS-DOS haviam assumido fortes posições no mercado, portanto
a Intel teve a preocupação de tornar o seu novo processador compatível com
este sistema. O 80386 não apenas permite comutar entre o modo real e o
modo virtual (tornando possível usar a memória estendida no ambiente
DOS, bem como usar programas de modo protegido e ainda assim ter
8-52 Hardware Total
acesso às funções do DOS, que operam em modo real), mas também
permite que tarefas no modo protegido possam operar no modo virtual 8086.
É o modo no qual é executado o Prompt do MS-DOS sob o Windows. Este
modo opera sob o modo protegido, em ambiente multitarefa, porém uma
tarefa neste modo tem o uso dos registradores de segmento idêntico ao do
8086. Significa que registradores CS, DS, ES e SS indicam o valor do
segmento em 16 bits, são adicionados de 4 bits “0” e somados com um offset
de 16 bits. Para um programa que opera neste modo, está sendo acessada
uma memória de 1 MB, porém o endereço linear gerado tem 32 bits. Através
do mecanismo de paginação, este 1 MB pode ser mapeado em quaisquer
páginas da memória física ou da memória virtual. Podemos assim ter vários
programas em modo virtual 8086 operando simultaneamente. No Windows,
isto equivale a abrir várias janelas com o Prompt do MS-DOS.

Multitarefa
Um computador pode executar vários programas ao mesmo tempo,
compartilhando seu tempo entre todos eles. Este tipo de ambiente é
chamado de multitarefa (multitask). Assim como o 286, o 80386 tem no seu
modo protegido, todos os recursos para operar em um ambiente multitarefa.
Um desses recursos é a diferenciação entre os níveis de privilégio das tarefas.
As tarefas do 386 podem ter níveis 0, 1, 2 e 3. O nível 0 é o único que tem
acesso a todos os recursos do processador, e deve ser usado pelo núcleo do
sistema operacional. Neste nível podem ser executadas as instruções para
gerenciamento das demais tarefas e gerenciamento de memória. Tarefas com
níveis 1 e 2 não podem executar essas instruções críticas, mas têm aceso a
algumas instruções não suportadas por programas comuns. Esses dois níveis
devem ser usados por módulos do sistema operacional e drivers. O nível 3 é
o mais baixo, e pode executar praticamente todas as instruções, exceto
aquelas mais críticas, só permitidas pelos níveis superiores. Programas
comuns devem operar no nível 3. A figura 19 mostra como são organizados
esses níveis.
Capítulo 8 – Arquitetura de processadores 8-53
Figura 8.19
Níveis de privilégio do 386.

Pipeline do 386
Pipeline é uma técnica amplamente utilizada nos processadores mais novos,
mas já era utilizada em pequena escala no 386. Seu funcionamento é
equivalente ao de uma linha de montagem de automóveis. Ao invés de
montar um automóvel de cada vez, a montagem é dividida em várias etapas
menores. Em cada estágio é feita uma parte da montagem, e o automóvel é
passado para o estágio seguinte. Todos os estágios trabalham ao mesmo
tempo, e no cômputo geral, a produção ao final do dia é bem elevada.

Nos processadores o trabalho também é dividido em vários estágios. Nos


modelos modernos, o número de estágios é bem elevado. No 386 existem 6
estágios:

1) Unidade de interface com o barramento (Bus Interface Unit) – Faz todos


os acessos à memória e ao espaço de E/S.

2) Unidade de pré-busca de código (Code Prefetch Unit) – Recebe as


instruções da unidade de interface com o barramento e as guarda em uma
fila de 16 bytes.

3) Unidade de decodificação de instruções (Instruction Decode Unit) –


Recebe as instruções da unidade de pré-busca e as converte em
microcódigo.

4) Unidade de execução (Execution Unit) – Executa as instruções em


microcódigo.

5) Unidade de segmentação (Segment Unit) – Traduz o endereço lógico,


formado por segmento e offset, em endereço linear de 32 bits.
8-54 Hardware Total

6) Unidade de paginação (Paging Unit) – Traduz o endereço linear em


endereço físico, faz as checagens de proteção de memória. Esta unidade tem
uma pequena cache (TLB) com as informações sobre as 32 páginas mais
recentemente acessadas.

Essas unidades podem ser vistas na figura 14. Observe ainda que os blocos
do 386 são interligados por um barramento interno de 32 bits.

Arquitetura do 80486
Explicando em poucas palavras, um processador 80486 é similar a um 80386,
acrescido de um coprocessador matemático 80387, mais 8 kB de cache L1
integrada. Existem entretanto outras diferenças na arquitetura interna, sendo
a principal delas, o aumento do número de estágios pipeline. A figura 20
mostra o diagrama interno do 486.

*** 75%
Figura
8.20
Diagrama interno do
486.

Entre as principais diferenças em relação ao 386, notamos a cache de 8 kB,


ligada à unidade de pré-busca por uma caminho de 128 bits. A fila de
instruções teve o tamanho dobrado para 32 bytes. As transferências internas
de dados são agora feitas por dois caminhos de 32 bits.
Capítulo 8 – Arquitetura de processadores 8-55
As unidades de decodificação e execução foram cada uma desmembrada
em 2 estágios pipeline. São portanto ao todo 5 estágios pileline, além do
estágio inicial de interface com o barramento e os dois estágios finais de
segmentação e paginação. Cada um desses estágios é capaz de executar sua
tarefa em um único clock, portanto o 486 pode executar na maior parte das
vezes, uma instrução a cada período de clock.

Figura 8.21
Pipeline do 486.

A operação dos estágios pipeline do 486 é mostrada na figura 21. São usados
5 estágios:

 Prefetch
 Decode 1
 Decode 2
 Execution
 Writeback

Cada estágio opera em um ciclo de clock. Quando uma instrução entra no


pipeline, demorará 5 ciclos para ser completada (lembre-se que existe um
estágio anterior, o de interface de barramento, e dois estágios posteriores, o
de segmentação e de paginação. Cada instrução leva 5 ciclos para atravessar
o pipeline da figura 21, mas assim que passa do primeiro para o segundo
estágio, uma segunda instrução chega ao primeiro estágio. Depois de 5
clocks, o último estágio estará completando a cada clock, a execução de uma
instrução. Na figura 21, os instantes t1, t2, t3 e t4 marcam a finalização das
instruções I1, I2, I3 e I4.

O pipeline do 486 realmente traz um grande aumento de desempenho.


Apesar de terem arquiteturas muito parecidas (sendo o pipeline mais
avançado, a principal diferença), um 486 é duas vezes mais rápido que um
386 de mesmo clock.
8-56 Hardware Total

O conjunto de instruções do 486 é o mesmo do 386 e do 387 reunidos, além


de algumas instruções para controle de cache e para suporte a
multiprocessamento.

Arquitetura do Pentium
Além do aumento de clock, o uso de arquitetura pipeline foi utilizada nos
processadores 386 e 486 para aumentar o desempenho. O Pentium também
tem suas operações de decodificação e execução realizadas por 5 estágios, tal
qual o 486. A grande evolução é a introdução da arquitetura superescalar,
através da qual podem ser executadas duas instruções ao mesmo tempo.
Podemos ver na figura 22 o diagrama do Pentium, no qual encontramos os
módulos U-Pipeline e V-Pipeline. Esses dois módulos operam de forma
simultânea, e graças a eles é possível executar duas instruções ao mesmo
tempo. Outro melhoramento é a adoção do barramento de dados com 64
bits, com a qual é possível reduzir os efeitos da lentidão da memória RAM.
O Pentium tem ainda uma cache L1 maior, dividida em duas seções
independentes, sendo uma para código e outra para dados. A unidade de
ponto flutuante foi reprojetada, e é muito mais rápida que a do 486.

** 75%
Figura
8.22
Diagrama interno do
Pentium.
Capítulo 8 – Arquitetura de processadores 8-57
Arquitetura superescalar
Uma arquitetura superescalar é aquela na qual múltiplas instruções podem
ser executadas simultaneamente. Podemos ver na figura 23 como os
pipelines U e V do Pentium executam instruções. A execução simultânea é
possível desde que se tratem de instruções independentes, ou seja, que a
execução da segunda operação não dependa do resultado da primeira.
Observe que no instante t1, as instruções I1 e I2 são finalizadas, e em cada
um dos instantes seguintes, duas novas instruções são finalizadas. Desta
forma o Pentium pode teoricamente executar duas instruções a cada ciclo.

Figura 8.23
Funcionamento dos estágios pipeline do
Pentium.

Nem sempre é possível executar instruções em paralelo. Os programas são


formados por instruções seqüenciais ou seja, um programa é uma seqüência
de instruções. O paralelismo funciona entretanto em boa parte dos casos,
pois mesmo com instruções seqüencias, muitas são independentes. Veja por
exemplo o seguinte trecho de programa:

MOV SI, 1000


MOV DI, 2000
MOV CX,100
MOVER: MOV AL,[SI]
INC SI
MOV [DI],AL
INC DI
DEC CX
JNZ MOVER

As instruções são alimentadas nas duas pipelines de forma alteranada, sendo


uma instrução para U e outra para V. A ordem de alimentação é mostrada
abaixo.

U-Pipeline V-Pipeline
8-58 Hardware Total
MOV SI, 1000 MOV DI, 2000
MOV CX,1000 MOV AL,[SI]
INC SI MOV [DI],AL
INC DI DEC CX
JNZ MOVER MOV AL,[SI]

Note que os 3 primeiros estágios de cada pipeline são de pré-busca e


decodificação, ou seja, são usados para identificar exatamente o que a
instrução deve fazer. A execução propriamente dita é feita apenas no quarto
estágio, e no quinto é feito o armazenamento de valores. Se neste momento é
preciso utilizar um operando que ainda está sendo calculado pela outra
pipeline, a operação não pode prosseguir. Toda a fila de instruções desta
pipeline congelada até que o dado necessário termine de ser calculado pela
outra pipeline e esteja disponível. Nesses instantes, apenas uma pipeline
trabalha enquanto a outra espera. Portanto em certos instantes existe a
execução paralela, mas em outros instantes o Pentium se comporta como o
486, executando uma única instrução de cada vez, de acordo com a
dependência entre as instruções que são alimentadas em cada pipeline.

Previsão de desvio
As dependências entre instruções podem causar uma paralisação em uma
das pipelines, mas assim que a dependência é resolvida, as instruções
prosseguem ao longo da pipeline. Pior situação é quando ocorrem desvios
condicionais. Não é possível nesse caso saber que caminho tomará a
seqüência de instruções antes da finalização de sua execução. Veja o
exemplo:

....
DEC CX
JNZ LAB01
LAB00: MOV AX,1000
ADD AX,DX
LAB01: MOV BX,2000
....

Ao encontrar a instrução JNZ LAB01, o processador não saberá se continua


a carregar as instruções a partir de LAB00 ou de LAB01. Um processador
486 simplesmente assume que o desvio não é tomado e prossegue na
seqüência, ou seja, supõe que o desvio não é realizado. Se o desvio
ocorresse, o conteúdo de todos os estágios era decartado e a pipeline era
carregada a partir do novo endereço. Isso resultava em um período de
latência de 5 ciclos, e só então o processador voltava a fornecer uma
instrução por clock.
Capítulo 8 – Arquitetura de processadores 8-59

O processador Pentium tenta “adivinhar” se um desvio vai ser executado ou


não. Esta predição é feita com base no histórico das últimas vezes que a
instrução foi executada. Quando um desvio é encontrado pela primeira vez,
o processador não tem dados para predizer se será executado ou não,
portanto parte do princípio de que não será executado. Ao passar pela
segunda vez por este endereço, tomando como base o resultado do último
desvio, poderá predizer com grande chance de acerto, se este desvio será
executado ou não. Veja o exemplo:

MOV SI,1000
MOV DI, 2000
MOV CX,100
LAB01: LODSB
STOSB
DEC DX
JNZ LAB01
MOV AX,1000
….

Quando o processador encontra pela primeira vez a instrução JNZ LAB01,


parte do princípio de que o desvio não será realizado, e assume que a
próxima instrução a ser executada é a seguinte (MOV AX,1000). Note
entretanto que estamos usando em CX um contador 100, e este desvio será
realmente executado para o endereço LAB01. Quando o processador
encontra pela segunda vez esta instrução, ele sabe a partir do seu endereço,
que na vez anterior o desvio foi feito. Esta informação é armazenada em dois
bits de histórico no BTB (Branch Target Buffer). Passará então a supor que
desvio será realizado, ou seja, depois de carregar a instrução JNZ LAB01,
carregará as instruções a partir da LODSB. Portanto nas 98 vezes seguintes
em que esta instrução for executada, o desvio será previsto de forma correta.
Na centésima vez o desvio não será realizado, já que o contador chegou ao
final, e o desvio será previsto de forma errada. Neste exemplo tivemos 98%
de acerto na previsão de desvio. A taxa de acerto é sempre alta quando são
feitos desvios para o início de um loop de instruções, mas é mais baixa
quando são encontrados desvios que não se repetem, ou que repetem pouco.
De qualquer forma é melhor acertar algumas vezes e errar outras, que parar
sempre que for encontrada uma uma instrução de desvio, como ocorria no
486 e processadores mais antigos.

Cada unidade de pré-busca possui duas filas de instruções. A primeira é


sempre preenchida de forma seqüencial. A segunda é usada apenas quando
é encontrado um desvio. Após a instrução JNZ do último exemplo, a fila
seqüencial seria preenchida a partir da instrução MOV AX,1000 e a fila
8-60 Hardware Total
alternativa seria preenchida a partir da instrução LODSB. O processador
passa a preencher a fila alternativa e paralisa a outra quando prediz que o
desvio será tomado, e deixa a fila alternativa paralisada e opera com a
principal quando prediz que o desvio não será tomado. O Pentium não
carrega portanto as instruções das duas ramificações, e sim, escolhe a mais
provável (tomando como base o histórico das últimas execuções da
instrução) e passa a carregar instruções a partir deste ponto.

O mecanismo de previsão de desvio é realmente um pouco complexo e nem


sempre acerta, mas em caso de erro na previsão, não prejudica o
processador. Ele evita que o processador pare sempre que encontra uma
instrução de desvio, e qualquer acerto é lucro. Note ainda que os resultados
só são efetivados depois que a previsão é confirmada, portanto uma previsão
errada não causa resultados errados, apenas necessita de mais 5 ciclos em
média para que os estágios pipeline voltem a operar com máxima eficiência.

Instrução CPUID
A partir do Pentium, os processadores Intel (o mesmo ocorre com
processadores de outros fabricantes) passaram a incluir no seu conjunto de
instruções, uma que fornece infromações que identificam o processador. É a
instrução CPUID (CPU Identification). Programas de diagnóstico e
programas que dão informações sobre a configuração de hardware utilizam
esta instrução para informar corretamente o modelo do processador, bem
como identificar suas características.

Microarquitetura P6
Esta arquitetura foi usada a partir de 1995, com o lançamento do Pentium
Pro, e sofreu algumas modificações posteriores, dando origem ao Pentium II,
Celeron e Pentium III, bem como suas versões Xeon.

Uma das principais características desta nova arquitetura é a introdução de


um “velho” conceito já usado há alguns anos em computadores mais
poderosos: a tecnologia RISC – Reduced Instruction Set Computer.

CISC e RISC
Os processadores Intel, até o Pentium inclusive, são considerados máquinas
CISC (Complex Instruction Set Computer). Significa que seu conjunto de
instruções é bastante complexo, ou seja, tem muitas instruções que são
usadas com pouca freqüência. Um programador de linguagem Assembly usa
com muita freqüência instruções MOV, CALL, RET, JMP, ADD e outras,
mas raramente usa instruções como XLAT e todas as opções de shifts e
Capítulo 8 – Arquitetura de processadores 8-61
rotates A instrução XLAT, por exemplo, poderia ser substituída pela
seqüência ADD BX,AX / MOV AL,[BX]. Teoricamente o uso de uma única
instrução teria uma execução mais rápida que se fossem usadas duas ou mais
instruções, mas na prática não é o que ocorre. Um processador com um
conjunto de instruções muito complexo perde muito tempo para decodificar
uma instrução. Além disso, um maior número de circuitos internos seria
necessário para a implementação de todas essas instruções. Muitos desses
circuitos são pouco utilizados a acabam apenas ocupando espaço e
consumindo energia. Maior número de transistores resulta em maior
aquecimento, o que impõe uma limitação no clock máximo que o
processador pode utilizar.

Por outro lado, a arquitetura RISC (Reduced Instruction Set Computer –


computador com conjunto de instruções reduzido) utiliza instruções mais
simples, porém resulta em várias vantagens. Instruções simples podem ser
executadas em um menor número de ciclos. Com menos instruções, a
decodificação de instruções é mais rápida e os circuitos do decodificador
passam a ocupar menos espaço. Com menos circuitos, torna-se menor o
aquecimento, e clocks mais elevados podem ser usados. Todas essas
vantagens compensam o fato de um processador RISC não ter instruções
“poderosas” como XLAT, por exemplo. Colocando tudo na balança, um
processador RISC consegue ser mais veloz que um CISC de mesma
tecnologia de fabricação.

Existe um caso clássico em um processador CISC usado em um computador


de grande porte produzido pela Digital (VAX 8600) nos anos 80. Ele tinha
uma instrução BOUND, usada para checar se um índice está dentro dos
limites permitidos por um array (a instrução BOUND do 80826 faz um
trabalho similar). Se fossem usadas instruções mais simples de comparação
do próprio processador do VAX 8600, a execução seria mais rápida que
com o uso da sua própria instrução BOUND. Isso dá uma idéia de como
instruções complexas tendem a reduzir a eficiência do computador. A única
vantagem aparente das instruções complexas é a economia de memória.
Entretanto isso já deixou de ser vantagem há muitos anos, devido à queda de
preços das memórias.

Há muitos anos se fala em abandonar completamente o velho conjunto de


instruções CISC da família x86 e adotar uma nova arquitetura, com um
conjunto de instruções novo. Isto traria entretanto um grande problema, que
é a incompatibilidade com os softwares já existentes, mas um dia será feita
esta transição (é o que a Intel espera fazer com a chegada do processador
Itanium). Enquanto isso, os fabricantes de processadores adotaram um novo
8-62 Hardware Total
método de construção dos seus chips. Utilizam internamente um núcleo
RISC, mas externamente comportam-se como máquinas CISC. Esses
processadores aceitam as instruções x86 e as convertem para instruções RISC
no seu interior para que sejam executadas pelo seu núcleo. Este processo
mostrou-se mais eficiente que tentar produzir novos processadores CISC. O
Pentium MMX foi o último processador totalmente CISC. Todos os novos
processadores utilizam um núcleo RISC e tradutores internos de CISC para
RISC.

Execução dinâmica
Processadores de microarquitetura P6 utilizam um mecanismo de execução
fora de ordem que a Intel chama de Dynamic Execution. A execução fora
de ordem é um recurso necessário para o paralelismo no processamento de
instruções. A execução dinâmica utiliza por sua vez, três técnicas:

 Previsão de desvio
 Análise dinâmica do fluxo de dados
 Execução especulativa

A previsão de desvio consiste em manter as unidades de busca e


decodificação buscando instruções a partir de um desvio condicional, ao
invés de simplesmente parar até que o desvio seja processado. Técnicas de
previsão de desvio mais avançadas fazem com que a taxa de acerto nas
previsões seja mais alta que nos processadores anteriores. Na previsão de
desvio, não é feito o carregamento das duas ramificações de um desvio
condicional, e sim, é escolhido um caminho mais provável e este passa a ser
carregado para execução.

A análise dinâmica do fluxo de dados consiste em monitorar as instruções


em andamento e determinar quais delas podem ser executadas antes de
outras sem prejudicar a intergridade dos dados. Nos processadores Pentium,
a checagem de dependências era feita apenas entre as instruções que
estavam sendo executadas nas pipelines U e V. Nos processadores de
microarquitetura P6, um número maior de instruções pode ser monitorado,
sendo determinada uma ordem de execução que tende a aproveitar melhor
as unidades de execução.

Execução especulativa
A execução especulativa é uma das três técnicas usadas na execução
dinâmica, e sem dúvida é uma grande inovação, por isso preferimos explicá-
la separadamente. Processadores de arquitetura P6 buscam constantemente
Capítulo 8 – Arquitetura de processadores 8-63
instruções na memória, fazem sua decodificação e as colocam em um pool
de instruções (figura 24). Podemos visualizar este pool como uma espécie de
“tanque” com instruções a serem executadas. As unidades de execução
trabalham constantemente executando as instruções deste pool que não
possuem dependências em relação a outras instruções. A ordem na qual
essas instruções são executadas não é necessariamente a mesma ordem na
qual elas se encontram nos programas. Uma unidade de retirada é
encarregada de finalizar as instruções, o que consiste em armazenar valores
finais na memória e nos registradores. Esta unidade de retirada é
encarregada de restabelecer a ordem original das instruções, conforme se
encontram nos programas.

Figura 8.24
Modelo simplificado de um processador
de microarquitetura P6.

Observe ainda na figura 24 que todo o acesso à memória é feito por


intermédio das caches. A unidade de busca e decodificação obtém
instruções da cache L1. Se a instrução a ser buscada não está na cache L1, é
feita automaticamente a busca na cache L2. Se a instrução não está na cache
L2, automaticamente será feita a busca na memória externa. O mesmo
ocorre na leitura e escrita de dados , sempre feitas a partir da seção de dados
da cache L1. A separação da cache L1 em seções de código e de dados
serve para não interromper o fluxo de instruções sendo buscadas quando é
preciso acessar dados.

O processadores Pentium nunca executavam antecipadamente instruções


posteriores a um desvio. Eles apenas “chutavam” qual dos ramos de um
desvio seria tomado e adiantavam a execução do ramo escolhido. Se
ocorresse um erro nesta previsão, o conteúdo da pipeline era simplesmente
8-64 Hardware Total
descartado. Na microarquitetura P6, as instruções são executadas fora de
ordem. Desta forma uma instrução posterior a um desvio poderia ser
executada antes do desvio ser processado. Se a previsão de desvio fosse
errada, teríamos uma instrução sendo processada indevidamente. Este
problema é resolvido pela unidade de retirada de instruções. Ela somente
retira as instruções que foram efetivamente executadas. Aquelas que foram
executadas antecipadamente e indevidamente são descartadas. Para evitar
que uma instrução descartada altere um registrador ou endereço de memória
indevidamente, a atualização dos registradores e da memória só é feita pela
unidade de retirada de instruções. Suponha por exemplo a seguinte
seqüência:

MOV AX,2000
DEC BX
JZ LAB01
MOV AX,1000
INC CX
LAB01: …

Como as instruções são executadas fora de ordem, as instruções MOV


AX,1000 e INC CX podem ser executadas antes da instrução JZ LAB01 ser
processada, caso a previsão de desvio tenha “chutado” que o desvio da
instrução JZ LAB01 não iria ser realizado. Entretanto se o desvio for
realizado, as instruções MOV AX,1000 e INC CX não deveriam ter sido
executadas. Por isso as unidades de execução não atualizam efetivamente os
registradores e a memória, e sim indicam que dados deverão ser
armazenados caso as instruções sejam realmente executadas.

Micro-ops
O núcleo dos processadores de arquitetura P6 é RISC. Sua unidade de
busca e decodificação converte as instruções CISC (x86) obtidas da memória
em instruções RISC. A Intel prefere chamar essas instruções RISC de micro-
ops.
Capítulo 8 – Arquitetura de processadores 8-65
Figura 8.25
Diagrama interno de
processadores P6.

Diagrama interno do processador


Na figura 25 vemos o diagrama interno de processadores de microarquitetura
P6. A unidade de busca de instruções (Instruction Fetch Unit) obtém
instruções CISC x86 da cache L1 de código e as envia para o decodificador
de instruções. Este decodificador faz a conversão dessas instruções para
micro-ops, ou seja, instruções RISC. Essas instruções são então enviadas ao
instrucion pool, onde são executadas fora de ordem. Note portanto que as
instruções encontradas no instruction pool não são instruções CISC, e sim,
micro-ops (RISC). A partir do decodificador de instruções, o processador é
totalmente RISC. A unidade de execução tem 5 estágios paralelos,
permitindo a execução de até 5 micro-ops por período de clock.

No diagrama da figura 25 vemos outros blocos importantes:

Branch Target Buffer – É um conjunto de ponteiros com endereços das


instruções resultantes de desvios. Seu uso é importante na previsão de
desvio.

Microcode Instruction Sequencer – Operações CISC simples são traduzidas


diretamente em uma única micro-op. Operações mais complexas podem ser
traduzidas em várias micro-ops. Ao invés de usar um decodificador
complexo capaz de converter todas as instruções, aquelas mais complexas e
8-66 Hardware Total
menos usadas são convertidas através de uma tabela encontrada neste
módulo. O Microcode Instruction Sequencer é portanto uma ROM
contendo micro-ops equivalentes às instruções CISC x86 mais complexas.

Memory Reorder Buffer – Este módulo recebe uma seqüência de operações


de leitura e escrita na memória e rearruma essas operações de forma mais
eficiente. Faz por exemplo com que as leituras sejam efetivadas antes das
escritas, evitando assim que o processador tenha que esperar muito por um
dado da memória.

Registre File – É o conjunto de registradores da arquitetura Intel: EAX,


EBX, ECX, etc.

Adições na arquitetura P6
O Pentium Pro foi o primeiro processador a utilizar esta arquitetura. Com o
Pentium II, algumas alterações importantes foram feitas, como a adição de
instruções MMX, maior eficiência na execução de programas de 16 bits e
alterações estratégicas na cache L2. Como sabemos, o Pentium Pro tinha 256
kB de cache L2 integrada, e o Pentium II, assim como as primeiras versões
do Pentium III, usavam uma cache L2 separada do núcleo, formada por
chips SRAM, com 512 kB e operando com a metade da freqüência do
núcleo.

O Pentium III recebeu novas instruções chamadas SSE. São novas instruções
MMX e para processamento de gráficos em 3D. Posteriormente o Pentium
III teve a sua cache L2 integrada ao núcleo. Esta cache passou também a ter
256 kB e opera com a mesma freqüência do núcleo. Finalmente o Pentium
III Tualatin teve esta cache aumentada para 512 kB.

Arquitetura do Pentium 4
O Pentium 4 e o Intel Xeon são os primeiros processadores a utilizarem a
nova arquitetura Netburst da Intel. Apesar de ter muitos pontos em comum
com a arquitetura P6, a Netburst é um projeto totalmente novo.
Capítulo 8 – Arquitetura de processadores 8-67
Figura 8.26
Diagrama interno do Pentium
4.

Um ponto notável desta arquitetura é a nova cache L1, chamada de


Execution Trace Cache. As caches L1 de processadores anteriores
armazenam instruções x86 (IA-32). As unidades de pré-busca e decodificação
lidam com instruções provenientes da cache L1 e as introduzem nas
pipelines ou no pool de instruções para que sejam executadas. Nos
processadores de arquitetura P6, as instruções provenientes dos
decodificadores são micro-ops (instruções RISC). Nas máquinas Netburst
como o Pentium 4, as unidades de pré-busca e decodificação obtém as
instruções diretamente da cache L2, e não da cache L1. Instruções já
decodificadas e convertidas em micro-ops são transferidas para a cache L1,
de onde as unidades de execução obtém as micro-ops a serem executadas. A
vantagem de operar desta forma é que nos programas sempre temos
instruções que são executadas repetidas vezes. Não é necessário decodificar
novamente instruções que foram executadas há pouco tempo, pois sua forma
já convertida em micro-ops ainda estará na cache L1. Portanto o trabalho de
decodificação é feito uma só vez, e é aproveitado novamente quando uma
mesma instrução é executada outras vezes.

Existe um certo mistério da Intel em relação ao tamanho da cache L1.


Apenas é divulgado que esta cache armazena 12k micro-ops, mas não o seu
tamanho em kB. Isto gera uma certa confusão, inclusive este autor já
divulgou erradamente como 12 kB, o tamanho da cache L1 do Pentium 4, o
que não está correto. São na verdade 12k micro-ops. Por outro lado, a Intel
não divulga oficialmente o tamanho de uma micro-op nas máquinas
Netburst. Apenas é divulgado que a eficiência desta cache de 12k micro-ops
é similar à de uma cache comum com tamanho entre 8 kB e 16 kB. Ou seja,
em termos de eficiência da cache L1, é similar à de um Pentium III e outras
8-68 Hardware Total
máquinas P6. Já a cache L1 de dados opera de forma similar à das caches L1
de outras arquiteturas. Seu tamanho é 8 kB.

Máquinas Netburst têm um pool de instruções capaz de manter 126


instruções em andamento. Nas máquinas P6 eram apenas 40 instruções. Isto
permite executar um número maior de instruções antecipadas, ou seja, fora
de ordem. A unidade de execução pode executar até 6 instruções por ciclo
de clock, resultando em alto grau de paralelismo.

Máquinas Netburst são classificadas como hyperpipelined. operando com


pipeline de 20 estágios (máquinas P6 operavam com 10 estágios). Usar
estágios menores significa que cada um dos estágios pode ser mais simples,
com um menor número de portas lógicas. Com menos portas lógicas ligadas
em série, é menor o retardo de propagação entre essas portas, e desta forma
o ciclo de operação pode ser menor, ou seja, a freqüência de operação pode
ser maior. Máquinas Netburst podem portanto operar com maiores
freqüências de operação que as máquinas P6 de mesma tecnologia. Usando a
mesma tecnologia de produção do Pentium III (0,18), o Pentium 4 é capaz
de atingir clocks duas vezes maiores.

De 32 para 64 bits
Os processadores usados em praticamente todos os PCs modernos utilizam a
arquitetura x86, também chamada de IA-32. O termo x86 é devido à origem
dos processadores usados no PC, começando com o a família 8086/8088.
Processadores 8086, 8088, 80186, 80188 e 80286 eram todos de 16 bits.
Significa que a maioria das suas instruções operavam com valores inteiros de
até 16 bits, e que praticamente todos os seus registradores internos tinham 16
bits.

O termo IA-32 é mais novo, e significa Arquitetura Intel de 32 bits. É


derivada do processador 80386. Todos os processadores usados nos PCs
modernos são derivados desta arquitetura. Em outras palavras, podemos
considerar o Pentium 4 e o Athlon como versões super velozes do 386,
acrescidos de alguns recursos, porém são todos baseados em conjuntos de
instruções compatíveis com o do 386. Os termos x86 e IA-32 são usados
como sinônimos, sendo que a AMD prefere usar o termo x86, enquanto a
Intel prefere usar IA-32.

Tanto a Intel como a AMD estão entrando na era dos 64 bits, cada uma com
sua própria arquitetura:
Capítulo 8 – Arquitetura de processadores 8-69
Intel: IA-64
AMD: AMD x86-64

Essas duas arquiteturas têm características distintas. Ambas são de 64 bits, ou


seja, utilizam registradores, valores e endereços de 64 bits, apesar de
poderem também manipular valores de 32, 16 e 8 bits.

Intel IA-64
A arquitetura IA-64 é incompatível com a IA-32. Isto significa que os
programas que usamos nos PCs atuais não funcionarão nos PCs baseados na
arquitetura IA-64. Para facilitar a transição entre as arquiteturas IA-32 e IA-
64, o processador Intel Itanium (o primeiro a ser produzido com a IA-64)
utiliza um tradutor interno de instruções IA-32 para IA-64. Desta forma
poderá utilizar os programas atuais, porém com desempenho reduzido.

A idéia não é comprar PCs com o Itanium para executar os programas


atuais, e sim, utilizar os novos programas IA-64. Porém enquanto não
estiverem disponíveis todos os programas na versão de 64 bits, poderão ser
usadas versões antigas (ou seja, atuais) de 32 bits. Inicialmente a arquitetura
IA-64 é voltada para servidores e workstations de alto desempenho para uso
em CAD, processamento científico e outras aplicações que exigem grande
quantidade de cálculos.

Para os PCs “normais”, a Intel continuará fornecendo processadores IA-32,


como o Pentium 4 e seus sucessores. Desta forma foi aberto um leque de
opções: novos processadores de 32 bits e novos processadores de 64 bits,
porém compatíveis com os de 32 bits.

AMD x86-64
A AMD também lançará processadores de 64 bits, entretanto foi adotada
uma arquitetura diferente, chamada AMD x86-64. Entendendo de forma
bem simples, esta arquitetura engloba a atual IA-32 (ou x86, de 32 bits) e
adiciona recursos de 64 bits. Desta forma os programas poderão operar com
valores de 64, 32, 16 e 8 bits. Os atuais programas de 32 bits funcionarão
perfeitamente nesta nova plataforma. Internamente os processadores
possuirão os mesmos registradores internos encontrados nas máquinas x86,
porém ampliados para 64 bits. É uma evolução parecida com a transição de
16 para 32 bits, a partir do 80386. No 386, todos os registradores e instruções
de 16 bits foram mantidos, e ainda ampliados para 32 bits.
8-70 Hardware Total
Os programas atuais para 32 bits continuarão funcionando normalmente,
com seu pleno desempenho, ao contrário do que ocorre na arquitetura IA-
64, que é incompatível com a IA-32. Temos aqui um pequeno problema: os
futuros programas de 64 bits para a plataforma IA-64 serão incompatíveis
com os programas de 64 bits para a arquitetura x86-64. Os fabricantes de
software terão que desenvolver seus programas nas duas versões, e quando
não o fizerem, o usuário terá que usar apenas os programas específicos para
sua arquitetura, Intel ou AMD.

Os primeiros processadores AMD de 64 bits possuem os nomes provisórios


de “ClawHammer e SledgeHammer”. Ambos serão processadores de 8a
geração, 64 bits e tecnologia de 0,13, com lançamento previsto para 2002.
O ClawHammer irá operar em sistemas para até 2 processadores, e o
SledgeHammer irá operar com sistemas para 4 e 8 processadores.

32 bits para vários anos


A chegada dos processadores de 64 bits não vai sepultar de uma ora para
outra a tecnologia de 32 bits. Podemos comparar com o que ocorreu na
transição de 16 para 32 bits. O 80386 foi lançado em 1985, mas o 286, de 16
bits, continuou sendo o processador mais usado até o início dos anos 90,
sendo finalizada a sua produção em 1993. A partir daí os PCs têm usado
apenas arquiteturas de 32 bits, desde o 386 até o Pentium 4 e o Athlon.

A transição de 32 para 64 bits não será diferente. Novos processadores de 32


bits continuarão sendo lançados e bastante utilizados em todos os PCs. Os
modelos de 64 bits começarão a ser usados nos PCs de alto desempenho,
como servidores e wortkstations, para depois de alguns anos entrar no
mercado de PCs comuns. Tanto é assim que o Pentium 4, lançado em 2000,
é inteiramente de 32 bits, e ganhará novas versões nos próximos anos. A
arquitetura de 64 bits da AMD, por sua vez, não abandona a atual de 32 bits,
portanto os programas atuais poderão continuar sendo usados por muitos
anos. Assim como hoje um Pentium 4 é capaz de executar programas de 16
bits, os processadores de 64 bits da AMD continuarão operando também
com 32 bits.

Processador Intel Itanium


A figura 27 mostra um processador Intel Itanium, o primeiro baseado na
arquitetura IA-64. No início do seu desenvolvimento era conhecido pelo seu
nome-código, Merced. É produzido na forma de um cartucho chamado
PAC418, o mesmo nome do soquete usado nas placas de CPU para este
processador. No interior do cartucho encontramos o processador
Capítulo 8 – Arquitetura de processadores 8-71
propriamente dito e os chips SRAM que formam a cache L3. As primeiras
versões do Itanium têm 2 MB ou 4 MB de cache L3. As caches L1 e L2 são
integradas ao núcleo do processador, e as placas de CPU ainda poderão usar
uma cache L4 opcional. O Itanium possui 15 unidades de execução e 256
registradores internos de 64 bits, sendo 128 para números inteiros e 128 para
números de ponto flutuante.

Figura 8.27
Processador Intel Itanium.

Além da cache L3, o Itanium possui uma cache L1 com 32 kB (16 kB para
código e 16 kB para dados) e cache L2 com 96 kB. Parece um tamanho
pequeno para uma cache L2, mas note que se compararmos com outros
processadores, é a cache L3 de 2 MB ou 4 MB do Itanium que faz o trabalho
que em outros processadores é feito pela cache L2, com 256 kB ou 512 kB.
Ainda baseado na tecnologia de 0,18, resultando na alta dissipação de calor
pelos seus 25 milhões de transistores do seu núcleo (sem contar os quase 300
milhões da cache L3), o Itanium terá sucessores baseados na nova tecnologia
de 0,13.

A figura 28 mostra em detalhes a vista superior do cartucho do Itanium.


Possui na sua parte lateral um conector exclusivo para alimentação. Existem
furos para a fixação do cooler e para fixar o cartucho na placa de CPU.
8-72 Hardware Total
Figura 8.28
Cartucho PAC418, vista superior.

A figura 29 mostra a parte inferior do cartucho. Clipes laterais fazem a


retenção do processador, ajudando a fixação no soquete. Como já
mostramos, o conector de alimentação fica na parte lateral do cartucho. Na
sua parte inferior ficam os contatos do processador propriamente dito.

Figura 8.29
Cartucho PAC418, vista inferior.

Clock interno e externo


As primeiras versões do Itanium operam com 733 e 800 MHz de clock
interno. Parecem valores baixos em comparação com o Pentium 4, que já
ultrapasssou a barreira dos 2 GHz. Note entretanto que tudo no interior do
Itanium ocorre com 64 bits, portanto tem condições de apresentar um
rendimento no mínimo duas vezes maior em comparação com o Pentium 4.
Além disso o Itanium oferece um grau de paralelismo muito superior ao do
Pentium 4. Não podemos comparar processadores de arquiteturas diferentes
levando em conta apenas o clock.
Capítulo 8 – Arquitetura de processadores 8-73
O clock externo das primeiras versões do Itanium é de 133 MHz com DDR,
produzindo resultados equivalentes ao de um clock de 266 MHz. Seu
barramento de dados tem 64 bits, portanto a taxa de transferência máxima
teórica é de 2133 MB/s.

Modelo Clock externo Clock interno Multiplicador


Itanium/733 133 MHz 733 MHz 5,5x
Itanium/800 133 MHz 800 MHz 6x

OBS: Esses são os modelos previstos ao final de 2001, antes do lançamento do Itanium. A Intel
atrasou o seu lançamento e trabalhava na nova versão do Itanium, o McKinley, com clocks
mais elevados.

Voltagens e consumo elétrico


Assim como ocorre com outros processadores modernos, o Itanium informa
as voltagens de que necessita. São informadas voltagens diferentes para o
processador e para a cache L3, através de pinos identificadores. A voltagem
do núcleo, nas primeiras versões do Itanium, obedecia à especificação da
figura 30. São suportados valores entre 1,250 e 1,600 volt, em intervalos de
0,025 volt.

Figura 8.30
Identificação da voltagem do núcleo do
Itanium.

Já a voltagem usada pelos chips da cache L3 do Itanium seguem uma


especificação que pode variar entre 1,650 a 2,100 volts, em intervalos de
0,050 volt, como vemos na figura 31.
8-74 Hardware Total
Figura 8.31
Identificação da voltagem para os chips
da cache L3 do Itanium.

O Itanium é um processador bastante quente. Os modelos de 733 e 800


MHz têm consumo dependente da quantidade de cache L3. Com 2 MB,
consomem cerca de 116 watts, e com 4 MB chegam a consumir 130 watts.
Todo este calor dificulta a produção de modelos com clocks mais elevados,
mas a situação irá melhorar depois da adoção da tecnologia de 0,13.

Barramentos de dados e endereços


O barramento de dados do Itanium opera com 133 MHz e DDR,
produzindo o mesmo resultado que o de um clock de 266 MHz. Opera com
64 bits, portanto apresenta uma taxa de transferência máxima teórica de
2133 MB/s. Até 4 processadores podem ser ligados em conjunto na mesma
placa.

Seu barramento de endereços tem 44 bits, permitindo acessar diretamente


uma memória física de até 16 Terabytes (17.592.186.044.416 bytes). Ao
acessar memória virtual, opera com endereços de 54 bits, podendo
endereçar até 16 Petabytes (18.014.398.509.481.984 bytes).

Futuros processadores Intel de 64 bits


Ainda são bastante escassas as informações sobre os processadores IA-64
posteriores ao Itanium. A própria Intel não divulga publicamente tais
informações, exceto em conferências. Estão previstos novos processadores
que por enquanto têm nomes códigos de McKinley, Madison e Deerfield.

O McKinley (possivelmente será chamado de Itanium II) deverá usar a


tecnologia de 0,13 e sua cache L3 será integrada ao núcleo. Irá operar com
clocks superiores a 1000 MHz. O Madison terá uma cache L3 maior
(possivelmente 8 MB) e um barramento externo mais veloz. O Deerfield
deverá ser uma versão de baixo custo do Madison, com cache L3 de apenas
1 MB, voltado para o mercado de PCs de baixo custo. Será uma espécie de
“Celeron de Itanium”.
Capítulo 8 – Arquitetura de processadores 8-75

/////////// FIM ///////////

Você também pode gostar