Capítulo 8. Arquitetura de processadores - PDF
Download "Capítulo 8. Arquitetura de processadores"
Vitorino Rosa Deluca
1 Arquitetura de processadores Capítulo 8 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
2 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 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
3 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 B8D: D8 ADD AX,BX 1B8D:0102 C3 RET 1B8D: PUSH SS 1B8D:0104 B03A MOV AL,3A 1B8D: D5 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 C 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.
4 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 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 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 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
5 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.
6 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 MOV C,E MOV D,M MOV M,A ; A=C ; C=E ; D=M, ou seja, a posição de memória indicada ; por HL ; 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 MVI A,15h MVI M,150 MVI L,32 ; Carrega o registrador C com 200 (decimal) ; Carrega o acumulador com 15 hexadecimal ; Armazena o valor 150 em [HL] ; 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: b 325q 8BC3h
7 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 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 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 STA 2000h ; Carrega A com o valor existente em [1000h] ; 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-8 Hardware Total LHLD 1000h SHLD 2000h ; Faz L=[1000h] e H=[1001h] ; 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 STAX B LDAX B STAX D ; A = [DE] ; [BC] = A ; A = [BC] ; [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.
9 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 MOV A,M STAX D ; Carrega DE com o valor 1000h ; Move para A o valor armazanedo no endreço 300h ; 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 ADD C ADD L ADD M ; A = A+B ; A = A=C ; A = A+L ; 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.
10 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 ADC D ADC M ; A = A+L+carry ; A = A+D+carry ; A = A+[HL]+carry ACI: Semelhante à instrução ADI, exceto pelo Carry também entrar na soma. Exemplos: ACI 90 ACI 84 ; A = A+90+carry ; A = A+84+carry SUB: Faz uma subtração de A com o registrador (A = A-registrador) ou com M. Exemplos: SUB D SUB C SUB M ; A = A-D ; A = A-C ; 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 SBB L SBB M ; A = A-C-carry ; A = A-L-carry ; A = A-[HL]-carry
11 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 SBI 73h ; A = A-2Fh-carry ; 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:
12 8-12 Hardware Total INX H INX D DCX B INX SP ; HL = HL+1 ; DE = DE+1 ; BC = BC-1 ; 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
13 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 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 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 AND , teremos o seguinte resultado: AND
14 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 ANA C ANA A ANA M ANI 3Fh ; A = A AND B ; A = A AND C ; A = A AND A ; A = A AND [HL] ; 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 b, 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 XRA C ORA L ORI 20h XRI 04h XRA A ; A = A XOR B ; A = A XOR C ; A = A XOR L ; A = A OR 20h ; A = A XOR 04h ; 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
15 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 CMP L CMP M CPI 4Ch ; Compara A com C ; Compara A com L ; Compara A com [HL] ; 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
16 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.
17 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 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: LXI H, : LXI D, : 0E 10 MVI C, : CD CALL A: C JMP : 7E MOV A,M 1021: 12 STAX D 1022: 23 INX H 1023: 13 INX D 1024: 0D DCR C 1025: C JNZ : 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
18 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 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
19 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.
20 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 OUT 40h ; Lê dado que está no endereço de E/S 70h ; 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.
21 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
22 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 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 Cod e Mnemonic Op Code Mnemonic Op Cod e Mnemonic Op Cod e Mnemonic Op Code Mnemonic Op Code Mnemonic 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 D 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 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 D 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
23 Capítulo 8 Arquitetura de processadores 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 Os seus registradores internos são de 16 bits, mas foram inspirados nos registradores do 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.
24 8-24 Hardware Total Figura 8.7 Registradores internos do 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 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:
25 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] [BP+valor] [SI+valor] [DI+valor] [BX+SI+valor] [BX+DI+valor] [BP+SI+valor] [BP+DI+valor] Exemplos: MOV BX,1000h MOV AL,[BX+15h] MOV BX,2000h MOV SI,100h MOV AL,[BX+SI+20h] ; Aponta para o endereço 1000h ; Lê para AL o valor que está em 1015h ; 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 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
26 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.
27 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 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
28 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 MOV CX,SI MOV DS,AX OR BX,CX ; Copia BX em AX ; Copia SI em CX ; Copia AX em DS ; 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] ADD CX,[2000h] CMP SI,[1020h] ; Carrega o valor do endereço 1000h em AL e do endereço 1001h em AH ; Carrega o valor de 16 bits dos endereços 2000h/2001h em CX ; 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]