cap08 - Arquitetura de processadores

cap08 - Arquitetura de processadores

(Parte 1 de 9)

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-2Hardware 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.

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:

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 processadores8-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ódigoAssembly
1B8D:0100 01D8ADD AX,BX
1B8D:0102 C3RET
1B8D:0103 16PUSH S
1B8D:0104 B03AMOV AL,3A
1B8D:0106 380685D5CMP [D585],AL
1B8D:010A 750EJNZ 011A
1B8D:010C 804E0402OR BYTE PTR [BP+04],02
1B8D:0110 BF86D5MOV DI,D586
1B8D:0113 C6460MOV BYTE PTR [BP+0],0
1B8D:0117 E85F0BCALL 0C79
1B8D:011A 8B7E34MOV DI,[BP+34]
1B8D:011D 007C1BADD [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-4Hardware 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 processadores8-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 ZZeroIndica se o resultado da operação foi zero CYCarryIndica se uma operação aritmética teve “vai um” ou “pede emprestado” PParityIndica a paridade do resultado da operação. SSignalIndica o sinal de uma operação, se foi positivo ou negativo ACAux. CarryCarry 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-6Hardware 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:

Capítulo 8 – Arquitetura de processadores8-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 F em hexadecimal). Os números inteiros positivos de 16 bits podem assumir valores entre 0 e 65.535 decimal (ou 0 a F 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]

(Parte 1 de 9)

Comentários