Tutoriais e Programação
AoA - Cap.3 - Processadores "hipotéticos"
Seg 26 Fev 2007 17:31 |
- Detalhes
- Categoria: Art of Assembly
- Atualização: Sábado, 27 Fevereiro 2010 17:12
- Autor: vovó Vicki
- Acessos: 8665
Para entender como é possível melhorar a performance de um sistema, está na hora de explorar a operação interna da CPU. Infelizmente os processadores na família 80x86 são excepcionalmente complexos. Discutir sua operação interna provavelmente causaria mais confusão do que esclarecimentos. Utilizaremos então os processadores 886, 8286, 8486 e 8686 (os processadores "x86"). Estes processadores imaginários são simplificações extremas de vários membros da família 80x86 e destacam características importantes da arquitetura 80x86.
Os processadores 886, 8286, 8486 e 8686 são todos iguais, exceto pela forma como executam instruções. Todos eles têm o mesmo conjunto de registradores e "executam" o mesmo conjunto de instruções. Esta afirmação contém algumas idéias novas - vamos destrinchá-las uma por uma.
{faqslider} :::: Os registradores da CPU ::::
Os registradores da CPU são posições de memória muito especiais construídas com flip-flops. Os registradores não fazem parte da memória principal - a CPU os implementa em chips. Vários membros da família 80x86 têm registradores de tamanhos diferentes. As CPUs do 886, 8286, 8486 e 8686 (x86 de agora em diante) têm exatamente quatro registradores, todos com capacidade de 16 bits. Toda aritmética e todas as operações de localização ocorrem nos registradores da CPU.
AX | Acumulador |
---|---|
BX | Registrador do endereço base |
CX | Contador |
DX | Registrador de Dados |
Como o processador x86 tem poucos registradores, vamos chamar cada registrador pelo seu nome próprio ao invés de nos referirmos a eles pelo seu endereço. Os nomes para os registradores do x86 podem ser vistos na tabela ao lado.
Além dos registradores citados, que estão disponíveis ao programador, os processadores x86 também têm um registrador apontador de instruções que contém o endereço da próxima instrução a ser executada. Há também um registrador flag que guarda o resultado de comparações. O registrador flag lembra se um valor era menor, igual ou maior quando comparado com outro valor.
Como os registradores são chips e são utilizados essencialmente pela CPU, eles são muito mais rápidos do que a memória. Acessar uma posição de memória requer um ou mais ciclos de clock, acessar dados num registrador geralmente gasta zero ciclos de clock. Portanto, devemos tentar manter variáveis nos registradores. Conjuntos de registradores são muito pequenos e a maioria dos registradores tem propósitos especiais que limitam seu uso como variáveis, mas eles são ainda um excelente local para armazenar dados temporários.
:::: :::: A unidade aritmética e lógica ::::A unidade aritmética e lógica (ALU - Arithmetic and Logical Unit) é onde a maioria das ações ocorrem dentro da CPU. Por exemplo, se quisermos adicionar o valor cinco ao registrador AX, a CPU
- Copia o valor de AX para dentro da ALU
- Envia o valor cinco para a ALU
- Instrui a ALU para somar esses dois valores
- Move o resultado de volta para o registrador AX
A Unidade de Interface do Barramento (BIU - Bus Interface Unit) é responsável por controlar os barramentos de endereço e de dados quando estes acessarem a memória principal. Se uma cache estiver presente no chip da CPU, então a BIU também é responsável por acessar os dados na cache.
:::: :::: A unidade de controle e o conjunto de instruções ::::Uma pergunta que se impõe neste ponto é "Como exatamente a CPU efetua a atribuição de tarefas?". Isto é feito fornecendo à CPU um conjunto fixo de comandos ou instruções para trabalhar. Tenha em mente que os projetistas de CPUs constroem estes processadores utilizando portas lógicas para executar estas instruções. Para manter o número de portas lógicas razoavelmente pequeno (dezenas ou centenas de milhares), os projetistas devem restringir obrigatoriamente o número e a complexidade dos comandos que a CPU reconhece. Este pequeno conjunto de comandos é o conjunto de instruções da CPU.
Os programas nos primeiros sistemas de computadores (antes de Von Neumann) eram frequentemente "montados" na forma de circuitos, isto é, as conexões do computador determinavam o tipo de problema que o computador resolveria. Para alterar o programa, alguém tinha que reconectar o sistema de circuitos - uma tarefa muito difícil. O avanço seguinte no projeto de computadores foi o sistema de computador programável, um que permitia ao programador reconectar com facilidade o sistema utilizando uma sequência de soquetes e conectores. Um programa de computador consistia num conjunto de linhas de buracos (soquetes), cada linha representando uma operação durante a execução do programa. O programador podia selecionar uma das várias instruções plugando um conector em um soquete em particular para obter a instrução desejada (Fig.14).
É claro que a maior dificuldade deste esquema era que o número de instruções possíveis era severamente limitado pelo número de soquetes que podiam ser colocados em cada linha. Contudo, os projetistas de CPUs rapidamente descobriram que, com uma pequena quantidade de circuitos lógicos adicionais, o número de soquetes poderia ser reduzido de n buracos para n instruções para lg(n) [log na base 2] buracos para n instruções. Fizeram isto atribuindo um código numérico para cada instrução e depois codificaram as instruções dando-lhes um número binário utilizando lg(n) buracos (Fig.15).
Esta adição requeria oito funções lógicas para decodificar os bits A, B e C do painel de sinais, mas os circuitos extras compensavam porque reduziam o número de soquetes que precisavam ser repetidos para cada instrução.
É óbvio que muitas instruções da CPU não são auto-suficientes. Por exemplo, a instrução move é um comando que move dados de uma posição para outra (por exemplo, de um registrador para outro). Portanto, a instrução move requer dois operandos: um operando origem e um operando destino. Os projetistas de CPUs geralmente codificavam esses operandos origem e destino como parte da instrução da máquina, certos soquetes correspondendo ao operando origem e certos soquetes correspondendo ao operando destino. A Fig.16 mostra uma possível combinação de soquetes para manipular os operandos. A instrução move moverá dados do registrador origem para o registrador destino, a instrução add somará o valor do registrador origem com o valor do registrador destino, etc.
Um dos primeiros avanços no projeto de computadores que a VNA propiciou foi o conceito de programa armazenado. Um dos grandes problemas do método de programação com o painel de sinais era que o número de passos do programa (instruções de máquina) ficava limitado pelo número de linhas de soquetes disponíveis na máquina. John Von Neumann e outros identificaram uma relação entre os soquetes do painel de sinais e os bits na memória. Perceberam que poderiam armazenar os equivalentes binários de um programa de máquina na memória principal, buscar cada programa na memória e carregá-lo num registrador de decodificação especial conectado diretamente ao circuito de decodificação de instruções da CPU.
O truque foi adicionar ainda mais circuitos à CPU. Este circuito, a unidade de controle (UC), busca os códigos das instruções (também conhecidos como códigos de operação ou opcodes) na memória e os move para o registrador de decodificação de instruções. A unidade de controle contém um registrador especial, o apontador de instrução, o qual contém o endereço de uma instrução executável. A unidade de controle busca este código de instrução na memória e o coloca no registrador de decodificação para que seja executado. Depois de executar a instrução, a unidade de controle incrementa o apontador de instrução e busca a próxima instrução na memória para que seja executada, repetindo isto sucessivamente.
Quando desenvolvem um conjunto de instruções, os projetistas de CPUs geralmente escolhem opcodes que são múltiplos de oito bits para que a CPU possa buscar instruções completas na memória com facilidade. O objetivo é atribuir um número apropriado de bits ao campo da classe da instrução (move, add, subtract, etc.) e aos campos de operandos. Escolher mais bits para campos de instrução permite que se tenha mais instruções e escolher bits adicionais para os campos de operandos permite que se selecione um número maior de operandos (por exemplo, posições de memória ou registradores). Estas são complicações adicionais. Algumas instruções têm apenas um operando ou, talvez, não tenham nenhum. Ao invés de gastar os bits associados a esses campos, os projetistas geralmente reutilizam estes campos para codificar opcodes adicionais, mais uma vez com o auxílio de alguns circuitos adicionais. A família de CPUs 80x86 da Intel leva isto ao extremo, com instruções variando de um a aproximadamente dez bytes de tamanho. Uma vez que isto é um pouco mais difícil de ser tratado neste estágio inicial, as CPUs do x86 utilizarão um esquema de codificação diferente e muito mais simples.
:::: :::: O conjunto de instruções do x86 ::::As CPUs da família x86 possuem 20 classes de instruções básicas. Sete destas instruções têm dois operandos, oito têm um único operando e cinco não têm operandos. As instruções são mov (duas formas), add, sub, cmp, and, or, not, je, jne, jb, jbe, ja, jae, jmp, brk, iret, halt, get e put. Os parágrafos seguintes descrevem como cada uma delas funciona.
A instrução mov, na verdade, são duas classes de instruções fundidas na mesma instrução. As duas formas da instrução mov são as seguintes:
onde reg é um dos registradores ax, bx, cx ou dx, const é uma constante numérica (utilizando notação hexadecimal) e mem é um operando especificando uma posição de memória. A próxima seção descreve as possíveis formas que o operando mem pode assumir. O operando "reg/mem/const" indica que este operando em particular pode ser um registrador, uma posição de memória ou uma constante.
As instruções aritméticas e lógicas têm as seguintes formas:
A instrução add adiciona o valor do segundo operando ao primeiro (registrador) operando, deixando a resultado no primeiro operando. A instrução sub subtrai o valor do segundo operando do primeiro, deixando a diferença no primeiro operando. A instrução cmp compara o primeiro operando com o segundo e salva o resultado desta comparação para que possa ser utilizada com uma das instrução de desvio condicional (descritas a seguir). As instruções and e or calculam as operações lógicas correspondentes em nível de bits sobre os dois operandos e armazenam o resultado dentro do primeiro operando. A instrução not inverte os bits de um único operando de memória ou registrador.
As instruções de transferência de controle interrompem a execução sequencial das instruções na memória e transferem incondicionalmente, ou depois de testar o resultado de instruções cmp anteriores, o controle para algum outro ponto na memória. Estas instruções incluem as seguintes:
As seis primeiras instruções desta classe permitem que se verifique o resultado de instruções cmp anteriores com valores maiores, maiores ou iguais, menores, menores ou iguais, iguais ou diferentes. Por exemplo, se compararmos os registradores ax e bx com a instrução cmp e executarmos a instrução ja, a CPU do x86 desviará para a posição de destino especificada se ax for maior do que bx. Se ax não for maior do que bx, o controle continuará com a próxima instrução do programa. A instrução jmp transfere incondicionalmente o controle para a instrução no endereço destino. A instrução iret retorna o controle de uma rotina de serviço de interrupção, a qual será discutida adiante.
As instruções get e put permitem a leitura e a escrita de valores inteiros. get pára e espera que o usuário entre com um valor hexadecimal e então armazena o valor dentro do registrador ax. put exibe (em hexadecimal) o valor do registrador ax.
As instruções restantes, halt e brk, não requerem quaisquer operandos. halt encerra a execução do programa e brk pára o programa deixando-o num estado em que ele pode ser reiniciado.
Os processadores x86 requerem um único opcode para cada uma das instruções, não apenas para as classes de instruções. Embora "mov ax, bx" e "mov ax, cx" sejam da mesma classe, elas devem ter opcodes diferentes para que a CPU possa diferenciá-las. Contudo, antes de olhar todos os opcodes, talvez fosse uma boa idéia aprender algo sobre todos os operandos possíveis que essas instruções possam precisar.
:::: :::: Modos de endereçamento no x86 ::::As instruções no x86 utilizam cinco tipos diferentes de operandos: registradores, constantes e três esquemas de endereçamento de memória. Estas formas são chamadas de modo de endereçamento. Os processadores x86 permitem o modo de endereçamento por registrador, o modo de endereçamento imediato, o modo de endereçamento indireto, o modo de endereçamento indexado e o modo de endereçamento direto. Os parágrafos a seguir explicam cada um desses modos.
Os operandos registradores são os mais fáceis de entender. Considere as formas a seguir da instrução mov:
A primeira instrução não executa absolutamente nada. Ela copia o valor do registrador ax para o próprio registrador ax. As três instruções restantes copiam o valor de bx, cx e dx para o registrador ax. Note que os valores originais de bx, cx e dx permanecem inalterados. O primeiro operando (o destino) não está limitado a ax; você pode mover valores para quaisquer dos registradores citados.
Constantes também são fáceis de serem tratadas. Considere as seguintes instruções:
Estas instruções são todas diretas. Carregam seus respectivos registradores com a constante hexadecimal especificada.
Há três modos de endereçamento que tratam do acesso a dados na memória. Esses modos de endereçamento têm as seguintes formas:
A primeira instrução utiliza o modo de endereçamento direto para carregar ax com o valor de 16 bits armazenado na memória começando na posição hexadecimal 1000. A instrução mov ax, [bx] carrega ax com a posição de memória especificada pelo conteúdo do registrador bx. Este é um modo de endereçamento indireto. Ao invés de utilizar o valor de bx, esta instrução acessa a posição de memória cujo endereço aparece em bx. Note as duas instruções a seguir:
É claro que a última é preferível. Contudo, há muitos casos onde o uso de endereçamento indireto é mais rápido, mais curto e melhor. Nós veremos alguns exemplos quando analisarmos individualmente os processadores da família x86 logo adiante.
O último modo de endereçamento é o modo de endereçamento indexado. Um exemplo deste modo de endereçamento de memória é
Esta instrução soma o conteúdo de bx com 1000 para produzir o valor do endereço de memória a procurar. Esta instrução é útil para acessar elementos de arrays, registros e outras estruturas de dados.
:::: :::: Codificando as instruções do x86 ::::Embora possamos atribuir arbitrariamente opcodes para cada uma das instruções do x86, é bom lembrar que uma CPU real utiliza circuitos lógicos para decodificar os opcodes e age de acordo com os mesmos. Um opcode de uma CPU comum utiliza um certo número de bits no opcode para identificar a classe da instrução (por exemplo, mov, add, sub), e um certo número de bits para codificar cada operando. Alguns sistemas (por exemplo, CISC = Complex Instruction Set Computers) codificam estes campos de uma forma muito complexa produzindo instruções muito compactas. Outros sistemas (por exemplo, RISC = Reduced Instruction Set Computers) codifica os opcodes de uma forma muito simples, mesmo que isto signifique gastar alguns bits a mais no opcode ou limitar o número de operações. A família Intel 80x86 é definitivamente CISC e tem um dos mais complexos esquemas de decodificação de opcodes jamais produzido. O propósito dos processadores x86 hipotéticos é apresentar o conceito de codificação de instruções sem a complexidade que a família 80x86 apresenta.
A instrução básica tem o tamanho de um ou de três bytes. O opcode da instrução é um único byte que contém três campos. No primeiro campo, os três bits mais significativos definem a classe da instrução. Eles fornecem oito combinações. Como foi dito acima, há 20 classes de instruções. Como não podemos codificar 20 classes de instruções com três bits, teremos que usar alguns truques para tratar das outras classes. Como mostrado na Fig.17, o opcode básico codifica as instruções mov (duas classes, uma onde o campo rr especifica o destino, e outra onde o campo mmm especifica o destino), as instruções add, sub, cmp, and e or. Há uma classe adicional: special. A classe de instrução special fornece um mecanismo que nos permite expandir o número de classes de instruções disponíveis. Voltamos já a este assunto.
Para determinar um opcode de uma instrução em particular, precisamos apenas selecionar os bits apropriados para os campos iii, rr e mmm. Por exemplo, para codificar a instrução mov ax, bx, selecionamos iii=110 (mov reg, reg), rr=00 (ax) e mmm=001 (bx). Isto produz a instrução de um byte 11000001 ou 0C0h.
Algumas instruções do x86 requerem mais do que um byte. Por exemplo, a instrução mov ax, [1000] que carrega o registrador ax com a posição de memória 1000. A codificação para este opcode é 11000110 ou 0C6h. Contudo, a codificação do opcode de mov ax,[2000] também é 0C6h. Estas duas instruções fazem coisas diferentes, uma carrega o registrador ax com a posição de memória 1000h enquanto a outra carrega o registrador ax com a posição de memória 2000h. Para codificar um endereço nos modos de endereçamento [xxxx] ou [xxxx+bx], ou para codificar a constante no modo de endereçamento imediato, é preciso que o opcode seja seguido pelo endereço de 16 bits ou pela constate, na sequência byte menos significativo e byte mais significativo. Assim, os três bytes codificados para mov ax, [1000] seriam 0C6h, 00h, 10h e os três bytes codificados para mov ax, [2000] seriam 0C6h, 00h, 20h.
O opcode special permite que a CPU do x86 expanda o conjunto de instruções disponíveis. Este opcode trata muitas instruções com zero e um operando, como mostrado nas duas figuras a seguir:
Há quatro classes de instruções com apenas um operando. A primeira codificação (00), mostrada na Fig.18b, expande o conjunto de instruções para um conjunto de instruções de zero operandos. O segundo opcode é também um opcode de expansão que fornece todas as instruções de desvio do x86.
O terceiro opcode é para a instrução not. Esta é a operação de not lógico em nível de bits, que inverte todos os bits no operando registrador destino ou na memória. O quarto opcode de um único operando não está determinado. Qualquer tentativa de executar este opcode parará o processador com um erro de instrução ilegal. Projetistas de CPUs frequentemente reservam opcodes não determinados como este para futuramente poder ampliar o conjunto de instruções (como a Intel fez quando passou do processador 80286 para o 80386).
Há sete instruções de desvio ou salto no conjunto de instruções do x86. Todas têm a seguinte forma:
A instrução jmp copia o valor imediato de 16 bits (endereço) que segue o opcode para o registrador IP. Portanto, a CPU extrairá a próxima instrução deste endereço alvo. Na realidade, o programa "pula" do ponto da instrução jmp para a instrução no endereço indicado.
A instrução jmp é um exemplo de instrução de desvio incondicional. Ela sempre transfere o controle para um endereço especificado. As seis instruções restantes são instruções de desvio condicional. Elas testam alguma condição e o desvio só acontece se a condição for verdadeira. Se a condição for falsa, simplesmente a próxima instrução é executada. Estas seis instruções, ja, jae, jb, jbe, je e jne permitem que se teste maior que, maior ou igual a, menor que, menor ou igual a, igual e diferente. Estas instruções são normalmente executadas imediatamente após uma instrução cmp, pois cmp seta as flags correspondentes a menor que e igual que as instruções de desvio condicional testam. Note que há oito opcodes de desvio possíveis, mas o x86 utiliza apenas sete deles. O oitavo opcode é um outro opcode ilegal.
O último grupo de instruções, as instruções sem operandos, aparecem na Fig.18c. Três destas instruções são opcodes de instrução ilegal. A instrução brk suspende a atividade da CPU até que o usuário a reinicie manualmente. Isto é útil para interromper um programa durante a execução para observar resultados. A instrução iret (interrupt return) retorna o controle de uma rotina de serviço de interrupção. Discutiremos rotinas de serviço de interrupção mais adiante. A instrução halt encerra a execução do programa. A instrução get lê um valor hexadecimal fornecido pelo usuário e coloca este valor no registrador ax. A instrução put devolve o valor do registrador ax.
:::: :::: Execução de instruções passo a passo ::::As CPUs do x86 não completam a execução de uma instrução num único ciclo de clock. Executam muitos passos para cada instrução. Por exemplo, uma CPU realiza os seguintes comandos para executar a instrução mov reg, reg/mem/const:,/p>
- Busca o byte da instrução na memória.
- Atualiza o registrador IP para apontar para o próximo byte.
- Decodifica a instrução para ver o que deve ser feito.
- Se necessário, busca um operando de 16 bits da instrução na memória.
- Se necessário, atualiza o IP para apontar para o operando.
- Se necessário, calcula o endereço do operando (isto é, bx+xxxx).
- Busca o operando.
- Armazena o valor encontrado no registrador destino.
Uma descrição de cada um dos passos pode ajudar a esclarecer o que a CPU faz. No primeiro passo, a CPU busca o byte da instrução na memória. Para isto, ela põe o valor do registrador IP no barramento de endereços e lê o byte naquele endereço. Isto levará um ciclo do clock.
Depois de buscar o byte da instrução, a CPU atualiza o IP de forma que ele aponte para o próximo byte do fluxo de instruções. Se a instrução corrente for uma instrução multibyte, o IP apontará para o operando da instrução. Se a instrução corrente for uma instrução de um único byte, o IP apontará para a próxima instrução. Isto toma um ciclo de clock.
O próximo passo é decodificar a instrução para ver o que ela deve fazar. Entre outras coisas, isto indicará se a CPU precisa buscar bytes de operandos adicionais na memória. Isto levará um ciclo de clock.
Durante a decodificação, a CPU determina os tipos de operando que a instrução requer. Se a instrução precisar de um operando constante de 16 bits (isto é, se o campo mmm for 101, 110 ou 111), então a CPU busca esta constante na memória. Este passo pode precisar de zero, um ou dois ciclos do clock. Ele requer zero ciclos se não houver operando de 16 bits, requer um ciclo se o operando de 16 bits for um word alinhado (isto é, começa num endereço par - veja a Fig.18e) e requer dois ciclos de clock se o operando for um word não alinhado (isto é, não começa num endereço par - veja a Fig.18f).
Se a CPU buscar um operando de 16 bits na memória, ela precisa incrementar o IP em dois para que ele aponte o byte seguinte ao operando. Esta operação toma zero ou um ciclo de clock. Nenhum ciclo se não houver operando e um se houver um operando.
A seguir, a CPU calcula o endereço do operando na memória. Este passo é necessário apenas quando o campo mmm do byte de instrução for 101 ou 100. Se o campo mmm contiver 101, então a CPU calcula a soma do registrador bx com a constante de 16 bits. Isto requer dois ciclos, um para buscar o valor de bx e outro para calcular a soma de bx com xxxx. Se o campo mmm contiver 100, então a CPU usa o valor de bx como endereço de memória - isto requer um ciclo. Se o campo mmm não contiver 100 ou 101, então este passo não ocupa ciclos.
Buscar um operando toma zero, um, dois ou três ciclos, dependendo do operando em questão. Se o operando for uma constante (mmm=111), então este passo não requer ciclos porque esta constante na memória já foi extraída no passo anterior. Se o operando for um registrador (mmm = 000, 001, 010 ou 011) então este passo ocupa um ciclo de clock. Se este operando for um word alinhado na memória (mmm=100, 101 ou 110), então este passo toma dois ciclos do clock. Se ele não estiver alinhado na memória, são necessários três ciclos de clock para obter seu valor.
O último passo da instrução mov é armazenar o valor na posição destino. Uma vez que o destino da instrução load sempre é um registrador, esta operanção gasta um único ciclo.
A instrução mov, no total, consome entre cinco a onze ciclos, dependendo dos seus operandos e de seus alinhamentos (endereço inicial) na memória.
A CPU faz o seguinte com uma instrução mov mem, reg:
- Busca o byte de instrução na memória (um ciclo do clock).
- Atualiza o IP para que aponte para o próximo byte (um ciclo do clock).
- Decodifica a instrução para ver o que fazer (um ciclo do clock).
- Se necessário, busca um operando da memória (zero ciclos se no modo de endereçamento [bx], um ciclo se no modo de endereçamento [xxxx], [xxxx+bx] ou xxxx e o valor xxxx imediatamente seguinte ao opcode inicia em um endereço par, ou dois ciclos do clock se o valor xxxx inicia em um endereço ímpar).
- Se necessário, atualiza o IP para apontar para o operando (zero ciclos se não existir tal operando e um ciclo se o operando estiver presente).
- Calcula o endereço do operando (zero ciclos se o modo de endereçamento não for [bx] ou [xxxx+bx], um ciclo se o modo de endereçamento for [bx] ou dois ciclos se o modo de endereçamento for [xxxx+bx]).
- Obtém o valor do registrador para armazenar (um ciclo do clock).
- Armazena o valor buscado na posição destino (um ciclo se for um registrador, dois ciclos se o operando for word alinhado na memória ou três ciclos se o operando for uma posição alinhada na memória em um endereço ímpar).
O tempo para os dois últimos ítens é diferente do outro mov porque a primeira instrução pode ler dados da memória; esta versão da instrução mov carrega seus dados de um registrador. Esta instrução toma cinco a onze ciclos para ser executada.
As instruções add, sub, cmp e or fazem o seguinte:
- Busca o byte de instrução na memória (um ciclo do clock).
- Atualiza o IP para apontar para o próximo byte (um ciclo do clock).
- Decodifica a instrução (um ciclo do clock).
- Se necessário, busca um operando constante da memória (zero ciclos se for o modo de endereçamento [bx], um ciclo se for o modo de endereçamento [xxxx], [xxxx+bx] ou xxxx e o valor xxxx imediatamente seguinte ao opcode iniciar em um endereço par ou dois ciclos se o valor xxxx iniciar em um endereço ímpar).
- Se necessário, atualiza o IP para apontar para o operando constante (zero ou um ciclo do clock).
- Calcula o endereço do operando (zero ciclos se o modo de endereçamento não for [bx] ou [xxxx+bx], um ciclo se o modo de endereçamento for [bx] ou dois ciclos se o modo de endereçamento for [xxxx+bx]).
- Obtém o valor do operando e o envia para a ALU (zero ciclos se for uma constante, um ciclo se for um registrador, dois ciclos se for um operando word alinhado na memória ou três ciclos for um operando alinhado na memória em um endereço ímpar).
- Busca o valor do primeiro operando (um registrador) e o envia para a ALU (um ciclo do clock).
- Instrui a ALU para somar, subtrair, comparar ou realizar and ou or lógico dos valores (um ciclo do clock).
- Armazena o resultado no primeiro operando registrador (um ciclo do clock).
Estas instruções requerem entre oito e dezessete ciclos do clock para serem executadas.
A instrução not é semelhante à descrita acima, apesar de poder ser um pouco mais rápida porque possui apenas um operando:
- Busca o byte de instrução na memória (um ciclo do clock).
- Atualiza o IP para apontar para o próximo byte (um ciclo do clock).
- Decodifica a instrução (um ciclo do clock).
- Se necessário, busca um operando constante da memória (zero ciclos se o modo de endereçamento for [bx], um ciclo se o modo de endereçamento for [xxxx], [xxxx+bx] ou xxxx e o valor xxxx imediatamente seguinte ao opcode iniciar em um endereço par ou dois ciclos do clock se o valor xxxx iniciar em um endereço ímpar).
- Se necessário, atualiza o IP para apontar para o operando constante (zero ou um ciclo do clock).
- Calcula o endereço do operando (zero ciclos se o modo de endereçamento não for [bx] ou [xxxx+bx], um ciclo se o modo de endereçamento for [bx] ou dois ciclos se o modo de endereçamento for [xxxx+bx]).
- Obtém o valor do operando e o envia para a ALU (zero ciclos se for uma constante, um ciclo se for um registrador, dois ciclos se for um operando word alinhado na memória ou três ciclos for um operando alinhado na memória em um endereço ímpar).
- Instrui a ALU para realizar o not lógico dos valores (um ciclo do clock).
- Armazena o resultado de volta no operando (um ciclo do clock se for um registrador, dois ciclos se for uma posição word alinhada na memória ou três ciclos de clock se for uma posição de memória em um endereço ímpar).
A instrução not precisa de seis a quinze ciclos para ser executada.
As instruções de desvio incondicional funcionam como mostrado a seguir:
- Busca o byte da instrução na memória (um ciclo do clock).
- Atualiza o IP para apontar para o próximo byte (um ciclo do clock).
- Decodifica a instrução (um ciclo do clock).
- Busca o operando no endereço alvo da memória (um ciclo se xxxx estiver num endereço par ou dois ciclos de clock se estiver num endereço ímpar).
- Atualiza o IP para apontar para o mais novo endereço (um ciclo do clock).
- Testa as flags da CPU para "menor que" e "igual a" (um ciclo).
- Se os valores das flags forem apropriados para o desvio condicional em particular, a CPU copia a constante de 16 bits para dentro do registrador IP (zero ciclos se nenhum desvio, um ciclo de clock se ocorrer o desvio).
O modo de operação da instrução de desvio incondicional é idêntica à instrução mov reg, xxxx exceto que o registrador destino é o registrador IP do x86 e não ax, bx, cx ou dx.
As instruções brk, iret, halt, put e get não são de interesse no momento. Elas aparecem no conjunto de instruções principalmente para programas e experiências. Não podemos simplesmente dar a elas contagens de ciclos já que elas podem levar uma quantidade indefinida de tempo para completar suas tarefas.
:::: :::: As diferenças entre os processadores x86 ::::Todos os processadores x86 compartilham o mesmo conjunto de instruções, os mesmos modos de endereçamento e executam suas instruções utilizando a mesma sequência de passos. Então qual é a diferença? Por que não inventar um processador ao invés de quatro?
A principal razão para realizarmos este exercício é para explicar as diferenças de performance relacionadas às quatro características de hardware: pre-fetch queues (filas de busca antecipada), caches, pipelines e projetos superescalares. O processador 886 é um "dispositivo" barato que não tem qualquer uma destas características especiais. O processador 8286 implementa as filas de busca antecipada. O 8486 tem uma pilha de instruções, um cache e uma pipeline. O 8686 tem todas as características acima com operação superescalar. Estudando cada um desses processadores poderemos analisar os benefícios de cada uma dessas características.
:::: {/faqslider}Fonte
- Art of Assembly de Randall Hyde.
- Tradução meio que livre da vovó Vicki.