Processador Amd K6

 
 

 

Elgio Schlemer
elgio@inf.ufrgs.br
 
Phillipe O. Navaux
navaux@inf.ufrgs.br
 
 
Introdução ao Processamento Paralelo e Distribuído
Curso de Pós graduação em Ciência da Computação
Instituto de Informática - CPGCC
Universidade Federal do Rio Grande do Sul
 
 
 
 
 
Resumo
 
 
Este trabalho apresenta as principais características do processador AMD-K6. Um estudo das técnicas utilizadas por este processador, bem como seu poder de processamento através de técnicas antes só vistas em processadores RISC.
 
Abstract
 
 
This work describes the AMD-K6?s main features. Provide one study of the used techniques in this processor, and its power processing by features before found in RISC processors only.
 
 
 Introdução
    Os processadores cada vez mais incorporam tecnologia antes só explorada pelos supercomputadores, como pipeline, hierarquia de memória, execução especulativa e fora de ordem, etc. Estas técnicas foram desenvolvidas no intuito de melhorar o desempenho do processador, diminuindo sua ociosidade e para compensar a distância de velocidade existente entre o processador e a memória.

    A técnica de pipeline consiste em dividir a execução de tarefas em estágios, de forma análoga à construção de automóveis em série. Teríamos então, várias instruções sendo executadas simultaneamente, mas somente uma será concluída por vez [KIT96].
    A hierarquia de memória foi desenvolvida para tentar aproximar a velocidade alta dos processadores a da memória, que ainda é muito lenta. Uma vez que o processador busca as instruções em memória e, sendo esta lenta, temos que a memória não consegue acompanhar o ritmo do processador, ficando este, muitas vezes ocioso. Resolve-se este problema colocando memórias mais rápidas próximas ao processador (denominadas cache) e provendo mecanismo de busca e armazenamento das instruções [KIT96, FER92].

    Este recurso, no entanto, trouxe outros problemas, como por exemplo, os desvios. Não adianta nada trazer para a cache um conjunto de 20 instruções se a primeira delas é um desvio. As outras 19 não serão executadas após a primeira. Para resolver este problema desenvolveu-se mecanismos de execução especulativa, que tenta descobrir estes desvios e tentar trazer para a cache o fluxo de instruções corretos [FER92].

    Com o advento de processadores Super Escalares, ou seja, capazes de executar mais de uma instrução simultaneamente, possibilitou-se explorar o paralelismo a nível de instruções. Outros problemas precisavam ser resolvidos, como as dependências de dados. Mesmo que duas instruções possam ser executadas simultaneamente (porque o hardware permite) elas não poderão ser se uma depende da outra, ou seja, o resultado da primeira será usado na segunda. Técnicas de execução fora de ordem e renomeação de registradores foram desenvolvidas para resolver estes e outros problemas de dependência de dados [FER92].

    Já aproveitando muitas dessas técnicas e ainda mantendo um custo de produção baixíssimo, o AMD-K6 aparece como uma boa opção de processador para equipar computadores pessoais. Neste texto será apresentado uma visão deste processador, começando por suas principais características. Uma análise mais concreta será feita de seus decodificadores internos, de suas unidades funcionais, bem como de sua unidade Central de Escalonamento de Instruções. No final, um rápido comentário sobre seu desempenho e algumas conclusões será apresentado. 

     
    Características básicas

    O AMD-K6 é, antes de tudo, um processador Super Escalar compatível com o conjunto de instruções x86 e com suporte a instruções Multimídia, denominada MMX. No entanto sua arquitetura interna é construída em cima de instruções RISC86. Isso é possível porque ele possui decodificadores que geram instruções RISC a partir das x86. Possui 7 unidades funcionais de execução divididas em estágios de pipeline. Sua cache interna é de 64Kb possuindo também mecanismos de execução especulativa e técnicas de resolução de dependência de dados e de controle [FER92]. Para melhorar o grau de aproveitamento de suas unidades funcionais, possui também mecanismos de execução de instruções fora de ordem. Com esses recursos, o AMD é capaz de executar até seis instruções RISC por clock.

    Possui 48 registradores, sendo que 24 deles são para uso geral e os outros 24 exclusivos para serem usados na renomeação (usado para resolver alguns casos de dependência de dados). Dos 24 de uso geral, 8 são os mesmos encontrados nas arquiteturas x86 (EAX, EBX, ECX, EDX, EBP, ESP, ESI e EDI).

    Figura 2.1 - Diagrama em blocos do AMD-K6
     
     
    Diagrama em blocos

    Conforme apresentado na figura 2.1, a execução de uma instrução começa pela sua busca na memória principal pela unidade predecode. Esta calcula o tamanho da instrução corrente armazenando-a na cache de instruções. As instruções ficam então disponíveis ao decodificador, que irá transformá-las em instruções RISC, disponibilizando-as ao Escalonador Central. O Escalonador, então, envia as instruções, agora RISC, da maneira mais eficiente possível para as respectivas Unidades de Execução. Todo este percurso é feito sem nenhum custo extra de tempo.

     
    Decodificador de x86 para RISC86

    Um dos maiores problemas das instruções x86 é quanto ao seu tamanho. Estas instruções não tem um tamanho fixo, podendo variar de 1 até 15 bytes, enquanto que as RISC possuem um tamanho fixo. O AMD-K6 considera três tipos de instruções x86: curtas, longas e vetoriais. As curtas (short) são aquelas que não ultrapassam sete bytes de tamanho; as longas (long) são as maiores que sete bytes e menores ou iguais a onze; instruções maiores que 11 bytes são denominadas vetoriais (vector).

    A arquitetura do AMD-K6, isto é, aquilo que é visível aos softwares que serão executados em máquinas que o utilizarem, é baseada no conjunto de instruções x86. Porém, sua micro-arquitetura, isto é, como realmente ele processa as instruções, é baseada no conjunto de instruções RISC 86. É necessário, então, que seja feita uma decodificação de cada instrução a ser executada para seu(s) equivalente(s) em RISC. Cada instrução x86 curta ou longa pode resultar em até quatro instruções RISC (ver exemplo na tabela 1).
     

    Tabela 1 - Exemplo de decodificação
     
    O K6 possui três conjuntos de decodificadores para executar esta tarefa, um para cada tipo de instrução, de acordo com o seu tamanho. Para instruções do tipo short, foi implementado duas unidades de decodificação e uma unidade para cada um dos outros dois tipos de instruções (long e vector). As instruções do tipo short e long são completamente decodificadas pela unidade, enquanto que as do tipo vector são iniciadas pelo decodificador e terminadas por uma unidade especial de procura com rotinas armazenadas em uma ROM.

    Na figura 3.1 pode ser visto um diagrama simples do funcionamento dos decodificadores. O buffer de instruções (cache nível 1) possui bits de predecodificação que possibilitam ao decodificador a correta classificação das instruções.

    Figura 3.1 - Diagrama do Decodificador
     

    Mesmo havendo quatro unidades de decodificação (duas short, uma long e outra vector) só é possível decodificar um tipo de instrução por clock, o que significa que em apenas um ciclo de clock o K6 é capaz de decodificar até duas instruções short ou uma long (as vector precisam ser enviadas para outra unidade). As instruções short geram até duas instruções RISC enquanto que as long até quatro - programas x86 típicos são compostos de 80% de instruções short. Isso possibilita ao sistema de decodificação gerar até quatro instruções RISC por ciclo de clock. Por isso que a saída do decodificador é composta de quatro instruções. Temos também um pequeno inconveniente nesse ponto: a saída deverá ser sempre de quatro instruções. Se por acaso a instrução for do tipo long, mas gerar apenas três instruções RISC, o decodificador gera uma quarta instrução sem efeito algum (NOP) para enviar as quatro instruções necessárias. Temos, então, a possibilidade remota de termos apenas uma instrução válida saindo do decodificador, e as outras três sem efeito algum (NOP).

     
     
    Tratamento de desvios

    Na Unidade de decodificação é tomada a primeira providência para tentar amenizar o impacto de desvios do fluxo de instruções. Estes desvios podem ser de dois tipos: desvios condicionais ou incondicionais [FER92]. Estatísticas revelam que um programa típico possui cerca de 10% de desvios incondicionais e de 10 a 20% de desvios condicionais [AMD97]. Os desvios incondicionais mais freqüentes são as chamadas CALL e seu respectivo RET, utilizados para desviar o fluxo para uma subrotina. Neste caso, o AMD provê uma pilha de endereço para retorno, empilhando o endereço da próxima instrução, quando encontra um CALL e desempilhando-o ao encontrar o RET respectivo.

    Já os desvios condicionais são mais difíceis de serem previstos. Isso porque sua resolução depende de resultados obtidos por outras instruções. O caso mais comum de desvios condicionais são os usados em instruções de alto nível do tipo "If-Then-Else", "While-DO" ou "For-do", ou seja, dentro de loops. Quando o decodificador encontra um instrução de desvio, ele consulta uma tabela de predição de desvios para decidir se este desvio deve ser considerado como tomado ou não. Este mecanismo atualmente consegue índices de até 95% de acertos enquanto que o Pentium II, por exemplo, atinge 90%. Este índice trata-se do maior que já se conseguiu em processadores.

    Após cada decodificação, as instruções, agora RISC, são enviadas à unidade de Escalonamento.

     
    Unidade de Escalonamento
    +
    Considerado o coração do processador, é o Escalonador quem provê o melhor aproveitamento das unidades funcionais. Suas funções são a de enviar instruções para as unidades e tentar resolver problemas de dependência de dados e/ou de controle, seja através da execução fora de ordem ou renomeação de registradores. A renomeação de registradores é usada para resolver dependências de dados[FER92] .

    Na figura 4.1 podemos ver o esquema do Escalonador. Ele trabalha com uma janela de instruções [FER92] de 24 instruções RISC, o que eqüivale a 12 instruções x86 curtas (short) ou a 6 longas (long ). Tipicamente o buffer corresponde a 12 instruções x86 (porque 80% de um código é composto por instruções short).
     

    Figura 4.1 - Escalonador Central

    Na figura 4.2 podemos ver a organização do buffer de instruções usado pelo escalonador. Verifica-se que o buffer recebe sempre seqüências de quatro instruções RISC por vez, mesmo que uma ou mais delas tenham de ser preenchidas por instruções NOP - que não executam nada.

     
     
    Figura 4.2 - Buffer do Escalonador Central
     
    Tomada de desvios
    A unidade de escalonamento já tem a informação de qual o possível caminho do desvio a ser tomado e as instruções subsequentes já foram decodificadas para RISC. Esta informação é oferecida pela unidade de decodificação. Observe que na verdade, ainda não se sabe se esta "suposição" é mesmo verdadeira, ou seja, se o desvio será ou não tomado. O Escalonador, entretanto, assume como verdadeira a suposição feita pelo decodificador e prossegue ao enviou de instruções normalmente. Se após a resolução do desvio (pela execução de instruções pendentes) for constatado que houve um erro na tomada do desvio, o conteúdo dos registradores afetados pelas instruções que não deveriam ser executadas serão restabelecidos. Na verdade, serão apenas descartados, pois em caso de instruções de desvio, o processador atualiza dados apenas em registradores temporários (dos 24 disponíveis).
     
    Unidades funcionais

    Como mencionado anteriormente, é o Escalonador Central o responsável para despachar as instruções às suas respectivas Unidades funcionais. Estas unidades são de sete tipos e cada uma delas é, ainda, dividida em estágios de pipeline.

    Suas sete unidades funcionais são: store, load, branch, Integer X, Integer Y, Integer Multimidea (MMX) e Floating Point.

    As duas primeiras são responsáveis por escrita e leitura de dados na memória e são divididas em dois estágios de pipeline. A unidade Integer X é responsável pela execução das instruções aritméticas básicas, como soma e multiplicação e a única diferença existente entre ela e a unidade Integer Y é que esta última é capaz de trabalhar com dados de 16 e 32 bits. A unidade Integer Multimidea é usada para executar instruções do tipo MMX. Interessante que esta unidade compartilha o controle de pipeline com a unidade Integer X. A unidade Floating Point resolve aritméticas envolvendo ponto flutuante e, por último, a unidade branch foi especialmente desenvolvida para resolver (definitivamente, ou seja, não apenas prever) desvios condicionais. Na figura 2.1 pode ser visto a disposição destas unidades no diagrama em blocos do K6.

     
    Organização da cache

    O K6 possui 64kb de cache, sendo esta dividida entre cache de instruções e de dados É designado 32kb para armazenar instruções x86 e outros 32kb para dados. Internamente são organizadas em setores, sendo cada setor composto de 64 bytes e cada um desses setores, novamente divididos em duas linhas de 32 bytes cada. A cache utiliza o protocolo MESI de coerência [STA96].

      Cache de instruções
    Na cache de instruções também é colocada a informação de predecode, gerada pelo Predecode logic. Na verdade, esta informação consiste apenas no tamanho da instrução.

    A última informação da cache (tabela 2) consiste em um bit MESI, que define se o dado contido na linha é Valido ou Inválido. O protocolo MESI define quatro tipos de estados: Alterado, Exclusivo, Compartilhado e Invalido (Modified, Exclusive, Shared e Invalid), mas no caso de instruções pode ocorrer apenas os estados Exclusive e Invalid, por isso apenas 1 bit.
     

    Tabela 2 - cache de instruções

    Cada entrada dessa cache é associada a uma entrada de uma tabela TBL [AMD98], usada para converter endereços lineares em físicos.
     
     

      Cache de dados

    Já na cache de dados, não há a informação predecode, mas necessite de 2 bits de MESI. Isso porque uma linha pode assumir todos os quatro estados previstos no protocolo MESI (tabela 3).
     

    Tabela 3 - Cache de dados

    Estes estados podem ser:

    - Modified - O dado presente na cache foi alterado e esta diferente da memória;
    - Exclusive - significa que este dado está coerente e não foi alterado.
    - Shared - O dado é compartilhado por outras caches.
    - Invalid - O dado e invalido (ou instruçao).

    Desempenho
     
     

    O AMD-K6 é o principal concorrente do Pentium II. De fato possui muitas características importantes que merecem certa atenção ao decidir pela compra [TOR98]. Sua principal vantagem é a relação custo-benefício, pois o Pentium II ainda é muito caro para a maioria dos usuários. Recentemente, porém, a Intel lançou uma versão econômica do Pentium II, denominada Celeron, com preços compatíveis ao AMD-K6. No gráfico da figura 6.1 podemos ver uma comparação de performance do AMD-K6 com o Celeron [AMD97]. Observe que mesmo a versão de 200 Mhz da AMD tem um índice melhor que o Celeron de 300Mhz.

    Figura 6.1 - AMD-K6 x Intel Celeron
     

    Já no gráfico da figura 6.2 vemos uma comparação de desempenho do processador AMD-K6-2 (inclui recursos 3D) com o Pentium II [AMD97].
     

     

    Figura 6.2 - AMD K6-2 x Intel Pentium II
     
     
     
    Conclusão
     
     

    Muitas das técnicas de aumento de performance já incorporam nossos processadores e não são mais exclusividade dos grandes computadores RISC. Entretanto, a arquitetura destes processadores de uso comercial ainda é baseada no conjunto de instruções x86 o que dificulta muito o uso destas técnicas. O principal problema decorre do fato do tamanho das instruções: enquanto as RISC?s possuem tamanho fixo, as x86 podem variar de um byte a dezenas. A solução encontrada pelos fabricantes foi introduzir decodificadores que traduzem cada instrução x86 para as respectivas instruções RISC.

    Dentre estes processadores o K6 é, sem dúvida, um forte representante. Principal concorrente do Pentium PRO e Pentium II, possui características interessantes que podem ser fatores decisivos na escolha. Talvez a principal vantagem seja a economia, não só pelo preço do chip em si, mas também pelo fato de não necessitar, o K6, de placa mãe exclusiva para ele. Enquanto o Pentium PRO e o Pentium II requerem placas exclusivas, o K6 pode ser instalado nas placas típicas desenvolvidas para processadores Pentium, desde que possuem configuração de clock adequada [TOR98].

    Desde sua implementação, o K6 ficou um pouco desacreditado. Suspeitava-se que, mesmo tecnicamente melhor e mais barato, não haveria condições de ser produzido em larga escala, o que afastou alguns fabricantes de computadores que preferiram criar acordos de "fidelidade" com a Intel e passar a vender micros "Intel Inside". Recentemente, no entanto, alguns fabricantes firmaram acordo com a AMD e passaram a fornecer ao mercado micros equipados com o K6. Dentre os principais fabricantes estão a IBM e COMPAQ. Esta competição já obrigou a Intel a lançar uma versão Light do seu processador Pentium II - chamado de Celeron - e a investir fortemente em publicidade, procurando manter a marca Intel como sinônimo de qualidade e confiança, mas acredito que deverá fazer muito mais do que isso se quiser manter-se na liderança mundial de processadores.


    Referências Bibliográficas
[AMD98] AMD. Amd-K6 Processor Data Sheet. Preliminary Information. March/1998. 328 pg.

[AMD97] AMD. Site oficial da AMD. http://www.amd.com.

[FER92] FERNANDES, Edil S. T.; SANTOS, Anna Dolesjsi. Arquiteturas Super Escalares: Detecção e Esploração do Paralelismo de Baixo nível. In: VIII Escola de Computação, Gramado-RS. Instituto de Informárica. UFRGS.Porto Alegre, 1992.

[KIT96] KITAJIMA, João Paulo. Arquiteturas de Computadores: Tendências até o final do século. In: IV Escola Regional de Informática, Canoas-RS: Universidade Luterana do Brasil. Anais. 1996. Pg 92-109.

[STA96] STALLINGS, Willian. Computer Organization and Architecture: Design for Performance. 4 ed. Printece Hall, New Jersey. 1996.
[TOR98] TORRES, Gabriel. K6 - Características básicas. In Site de hardware. http://www.gabrieltorres.com/k6.html.