Universidade Estadual de Maringá Centro de Tecnologia Departamento de Informática ESTUDO SOBRE A ARQUITETURA RISC Carlos Sica, 1999 1. Arquitetura RISC (Reduced Instruction Set Computing) 1.1. Histórico Inicialmente os computadores tinham poucas instruções e um ou dois modos de endereçamento Num segundo passo, surgiram os computadores microprogramados, com um conjunto de instruções extremamente complexo no nível de máquina, essa linguagem de máquina era interpretada por microprograma que ficam em ROM, o que implica em não modificar, apesar de ser um programa. Esta tecnologia levou a criação de computadores (ex. VAX) com 200 instruções e 12 modos de endereçamento. A linguagem de alto nível começou a predominar e a distância semântica (semantic gap) complicou a construção de compiladores. Instruções de alto nível do tipo: if, while, case, etc., devem ser traduzidas para instruções de baixo nível tipo: mov, add e jump. A solução para reduzir esta distância foi elevar o nível da linguagem de máquina, o que era analisado positivamente por alguns autores, que desejavam instruções de máquina com mais campos e menos registradores. Também, em outros tempos, a velocidade da RAM era muito mais baixa que a da CPU, além disso, 10 vezes mais baixa que a ROM, o que criou o desejo de que todos as instruções fossem resolvidas em microcódigos ao invés de programação normal. 1.2. Regras Sacrifique tudo para reduzir o tempo de ciclo da via de dados. Microcódigo não é mágico. 1.3. Filosofia básica 1. Analisar as aplicações para encontrar as operações chave e elaborar uma máquina ótima para as realizadas pelos programas alvo. Segundo Tanenbaum - 1992, quase 50% das instruções de um programa são comandos de atribuição, 23% de comandos de seleção, 15% de chamadas à funções, 6% de laços de repetição e, 10%, de outros comandos. 2. Projetar uma via de dados que seja ótima para as operações chave. Este é o coração de uma máquina RISC. Composto pelos registradores, pela ULA e pelos barramento internos que os conectam. Em um único ciclo, os operandos devem ser lidos dos registradores, operados pela ULA e o resultado armazenado em um registrador. Operando1 Resultado Operando2 3. Projetar as instruções que executem as operações chaves, estas devem utilizar de forma ótima as vias de dados, ou seja, cada instrução deve utilizar um único ciclo da via de dados. 4. Adicionar novas instruções somente se elas não diminuírem a velocidades da máquina 5. Repetir este processo para outros recursos 1.4. Instruções por ciclo de via de dados A característica mais importante de uma máquina RISC é que as instruções são completadas em um único ciclo de via de dados. Se isso não ocorre, ela não pode fazer parte do conjunto de instruções. Isto acarreta em não ser possível ter instruções como de multiplicação, o que é resolvido por uma série de operações de soma e deslocamento. Um instrução RISC é muito parecida com uma microinstrução. Uma operação de alto nível precisa de muitas instruções para ser completada, por outro lado, uma instrução CISC pode levar, muitos ciclos para ser completada devido a interpretação pelo microprograma. 1.5. Registradores Os compiladores escritos para máquinas RISC fazem uso intenso de registradores, com intenção de reduzir o tráfego na via de dados em comunicação com a memória. Um processador RISC não tendo microcódigo, não ocupa área de memória para tal tipo de operação, portanto, pode construir um grande número de registradores utilizando esse espaço. 1.5.1. Organização dos registradores Os registradores são organizados na forma de janelas sobrepostas. Apesar da CPU possuir muitos registradores, em média 512, nesta técnica apenas um conjunto de registadores, em geral de 32 registradores de 32 bits, é visível para o programa a cada momento. Este conjunto é ainda dividido em subconjuntos conforme mostra a figura abaixo. R0..R7 32 bits Variáveis globais e ponteiros R8..R15 Parâmetros de entrada R16..R23 Variáveis locais R24..R31 Parâmetros de saída Utilizados por todos os procedimentos, o compilador decide o que colocar aqui. Evita o uso de pilha na memória, que é utilizada apenas em caso de exceder 8 parâmetros. Em geral 8 variáveis são suficientes para um procedimento, excesso vai na pilha. Parâmetros para os procedimentos chamados (entrada para eles). Por este formato, facilmente podemos concluir que os registradores de variáveis globais podem ser os mesmos para todos os procedimentos, assim, não há necessidade de se ter fisicamente um conjunto para cada. Os parâmetros de saída, que são entradas para o procedimento chamado, também podem ser sobrepostos economizando espaço. ProcA Globais Entrada Locais Saída ProcB Globais Entrada Locais Saída ProcC Globais Entrada Locais Saída ProcD Globais Entrada Locais Saída Quando o procedimento A chama o procedimento B passando parâmetros para ele, a área é comum, este subconjunto de registradores são sobrepostos. 1.5.2. Estudo comparativo: REGISTRADORES x CACHE Na arquitetura RISC, os registradores são organizados por janelas sobrepostas, e servem para armazenar as variáveis e parâmetros que são acessadas mais rapidamente. Esta filosofia coincide com a filosofia da memória cache utilizada pela maioria das arquiteturas conhecidas atualmente. 1.5.2.1. Variáveis locais No RISC todas as variáveis locais, com as exceções discutidas no item 1.5.1, são armazenadas em registradores. Já, no caso da cache, isto é feito apenas com as variáveis mais recentemente utilizadas. A vantagem da cache aparece quando se discute a transferência de funções ou trechos de programas para ela, o que seria impossível para os registradores. Porém, uma desvantagem aparece justamente neste ponto, pois, a transferência é feita por blocos e, quando se trata de variáveis, algumas que são carregadas na chache são serão utilizadas. 1.5.2.2. Variáveis globais Semelhante à técnica anterior. Estudos mostram que existe um bom número de variáveis globais, porém, nem todos são fortemente utilizadas. A cache, como guarda as mais recentemente utilizadas, com o tempo, terá a vantagem de guardar apenas aquelas mais necessárias. 1.5.2.3. Endereçamento de memória No caso de procedimentos ou funções, nos registradores, os dados são movimentados entre eles e a memória com baixa freqüência e, como a cache, em geral é pequena e de uso geral, outras informações podem sobrepor os blocos já carregados, incorrendo no atraso de execução. Para um registrador ser acessado basta conhecer o seu número e o da janela. Atividade simples e rápida. Porém, para acessar a memória cache, um endereço completo deve ser calculado e a complexidade deste cálculo é diretamente proporcional à complexidade do modo de endereçamento e, sabemos que na arquitetura CISC, isto pode ser muito complexo. A cache é realmente tão rápida quanto um registrador, porém, o acesso a ela é demasiadamente lento. 1.6. Acesso à memória É impossível acessar a memória em um único ciclo. Neste caso a arquitetura RISC tem que abrir mão da regra básica e aceitar pelo menos duas instruções maiores: leitura e escrita na memória. Instruções comuns continuam tendo apenas registradores como operandos, o que limita os modos de endereçamento, excluindo por exemplo o direto, o indexado e o indireto. Assim, apenas duas instruções estabelecem a comunicação da CPU com a memória, ainda assim, devem utilizar registradores específicos. Há que se analisar também a questão de instruções como as necessárias para trabalhar com memória virtual, multiprocessamento e assim por diante. Talvez estas tenham sido o grande problema de se manter a ideologia da arquitetura RISC proposta, então o conjunto foi incrementado com novas instruções para executar tais tarefas. 1.7. Pipelining Pensemos no seguinte exemplo: um homem sozinho assenta os azulejos de uma cozinha. Ele precisa buscar cada peça na pilha, acomodar a massa, colocar o ladrilho. Se ele utilizar os serviços de um ajudante, pode eliminar a tarefa de buscar os azulejos e se concentrar nop assentamento. Simples, duas pessoas trabalhando termina o serviço mais rápido. A técnica do pipelining é muito semelhante ao exemplo acima e também à uma linha de montagem de carros. Cada funcionário faz sua tarefa e no final o carro está pronto mais rápido que se cada pessoa montasse um deles. Uma CPU pipelined deve permitir a execução total da instrução por estágios, por exemplo, busca da instrução na memória; execução da instrução e referências memória (quando houver). Se utilizando esta técnica uma instrução for iniciada a cada ciclo, mesmo que ela demore mais de uma para ser finalizada, em média a regra estaria cumprida. Em cinco ciclos, cinco instruções foram iniciadas. Busca da instrução Execução da instrução Referência a memória 1 A 2 B A 3 C B 4 D C B 5 E D A instrução A é buscada no primeiro ciclo e executada no segundo. Já a instrução B utiliza dois ciclos para terminar sua operação, porém, não interfere na busca e execução da instrução C, e mais, se a instrução C não utilizar o registrador que está sendo atualizado pela instrução C, ela pode terminar sua operação sem problemas e a máquina continua trabalhando em velocidade total. Caso isto não aconteça, é necessário inserir uma instrução NOP para esperar o término daquela instrução de dois ciclos, o que, naturalmente atrasa o processamento geral. Busca da instrução Execução da instrução Referência a memória 1 A 2 B A 3 C B 4 NOP NOP 5 D C B Para amenizar este tipo de problema, e muitas vezes realmente solucioná-lo, o compilador deve ser bom o suficiente para garantir que a próxima instrução, não vai utilizar o dado que está sendo trazido da memória. Isto pode ser feito com uma instrução que não seja a que esteja na seqüência escrita pelo programador. O compilador deve organizar a execução das instruções visando o resultado final, tapando os "buracos" que surgirem por execução de instruções de mais de um ciclo, com "coisas úteis" ao ínves de NOPS's. Outras instruções além da que acessam a memória podem atrapalhar o processamento. Um JUMP condicional, onde o compilador não pode prever o resultado, obriga algumas vezes o esvaziamento do pipeline para executar o desvio. Nestes casos as técnicas utilizadas se chamam, repectivamente, carga atrasada e desvio atrasado, ambas resolvidas pelo compilador. 1.8. Ausência do microcódigo As instruções geradas por um compilador para uma máquina RISC, executam diretamente no hardware. A eliminação do nível de interpretação, é o fato que influencia diretamente na velocidade do computador. Um programa compilado para CISC tem menos instruções assembly que para um RISC, o que é vantajoso na economia de memória, porém, não na velocidade de execução. 1.9. Formato das instruções Como as instruções são decodificadas pelo hardware a necessidade de se criar um formato fixo para elas se torna claro. Cada bit que forma uma instrução entra diretamente no decodificar ou até mesmo em outras partes do hardware. opcode c destino fonte i offset Neste exemplo o mnemônico é composto pelo código de operação, um sinalizador para instruções condicionais, um campo para endereçar o registrador destino e um para o fonte, outro sinalizador para o modo de endereçamento imediato ou não e finalmente o offset do endereço do operando. 1.10. Modos de endereçamento 1.10.1. Modo imediato (i = 1) Para instruções com um operando que faz parte da instrução, o segundo operando é uma constante descrita no offset. 1.10.2. Modo direto Para operandos em registrador (i = 0), o primeiro operando seria o conteúdo do registrador fonte e, o segundo, endereçado pelos bits menos significativos do offset, consequentemente, o resultado armazenado no registrador destino. 1.10.3. Modo indexado Para instruções de leitura e escrita na memória, o endereço seria composto pelo offset somado ao registrador fonte, criando o modo indexado. 1.10.4. Modo indireto por registrador O decodificador de instruções quando encontra o offset igual a zero, traduz o endereço como indireto por registrador, pois, o endereço do operando seria o contido no registrador fonte. 1.10.5. Outros modos O compilador pode criar outros tipos de modos, traduzindo-os para os básicos, por exemplo, no projeto RISCII, criou-se um JUMP condicional relativo ao ponteiro de instruções ou contador de programa (IP ou PC), concatenando os três campos de baixa ordema para formar um offset de 19 bits com sinal, podendo assim, se deslocar para cima ou para baixo. 1.10.6. Conclusão O crescimento do número de modos de endereçamento em uma máquina, implica na perda de velocidade e também no aumento da complexidade. Porém, apesar do nome, uma máquina RISC poderia ter muitas instruções, contanto que elas executassem em um único ciclo de via de dados e tivessem o formato fixo. O comprometimento nesse caso, seria o de construir uma unidade de decodificação bastante complexa, já que ele cresceria exponencialmente com o número de instruções. 2. Compiladores 2.1. Compiladores x hardware Todo esse estudo conduz para um hardware mais simples quanto possível, não é necessário muito esforço para concluir que a complexidade do compilador cresce proporcionalmente à simplicidade do conjunto de instruções, que no RISC esta diretamente ligado ao hardware. Instrução em alto nível compilador Código de máquina Quando chega-se justamente ao ponto mais controverso no debate entre CISC x RISC, o desenho do compilador. Os defensores do CISC alegam que a distância semântica conduz a ineficiência de execução, programas excessivamente grandes e compiladores complexos e argumentam que a disponibilidade de instruções de alto nível e a habilidade de especificar múltiplos operandos baseados na memória, simplifica o desenho do compilador. Naturalmente, isto leva diretamente para um conjunto de instruções grande, dezenas de modos de endereçamento e muitas declarações da linguagem de alto nível implementadas em firmware. Surgem divergências entre as estruturas estudadas e as implementadas, como exemplo pode-se citar a instrução LOOP utilizada já no PC-xt para controle de laço de repetição. Esta instrução repete um bloco de instruções por um número fixo de vezes como o "parafaça", mas faz isto, pelo menos uma vez como o "repita-até". mov cx, 32 Aqui: ;estabelece o critério de parada .... .... loop aqui ;decrementa cx, e desvia para aqui se cx > 0 Este bloco poderia facilmente ser traduzido com a utilização de decrementos, comparação e desvio condicional, porém, nota-se aqui que a arquitetura CISC não economiza instruções. Fica então a pergunta: qual é a arquitetura que melhora o desenho do compilador? Sabendose que este compilador deve ser um compilador otimizador, irá produzir códigos de execução mais rápidos. Uma vez que as instruções devem ocupar um ciclo em média, o compilador deve reorganizar a seqüência das instruções, de forma a tapar os "buracos" gerados pelas de maior tempo. 2.2. Otimização dos compiladores Um programa escrito em linguagem de alto nível, não explicita os registradores que serão utilizados, ao invés disso, faz referências simbólicas aos valores através das variáveis criadas. Além disso, não explicita se o valor associado à variável será guardado num registrador ou na memória. Como sabe-se que os registradores são mais rápidos que a memória, é desejável que sejam largamente utilizados. Cada variável passível de ser guardada em um registrador, recebe um tratamento especial. O compilador pode criar um número ilimitado "registradores virtuais" para armazená-las e, a partir daí, compartilhar o registrador real de acordo com alguma técnica específica. Claro, isto é feito dentro da limitação da CPU e muitas variáveis vão obrigatoriamente para a memória. Os compiladores mantém esta filosofia de forma transparente para o programador, mas alguns, como é o caso da linguagem C, oferecem a possibilidade do programador decidir quais variáveis devem, sempre que possível, serem armazenadas em registradores, para tanto, ao declarar uma variável, ao invés de escrever: int soma; escreve-se: register int soma; obrigando então, o compilador a priorizar os registradores para aquela variável. As arquiteturas RISC utilizam, freqüentemente, a técnica dos grafos coloridos para otimizar o uso de registradores pelas variáveis. Está técnica não é discutida nesta publicação. 3. Estudo comparativo: RISC x CISC 3.1. Histórico A tecnologia RISC foi desenvolvida pela IBM nos anos 70 e o primeiro chip surgiu em 1980. Sua proposta baseou-se em um conjunto reduzido de instruções, sendo definidas apenas as mais freqüentemente utilizadas e se evitando o uso de microcódigos. As instruções também seriam simplificadas, trabalhando com apenas um operando. As operações enfatizavam o uso de registradores, sendo o acesso à memória limitado a instruções tipo leitura e escrita na memória (load/store). Assim, o processador gastaria apenas um ciclo por instrução. Porém, o que sucedeu não foi tão simples assim, pois havia muita dificuldade em se escrever programas complexos utilizando um conjunto muito reduzido de instruções. Então este conjunto foi incrementado com novas instruções, como as necessárias para trabalhar com memória virtual, multiprocessamento e assim por diante. A tecnologia RISC começou a ser promovida no mercado com o surgimento das estações de trabalho científicas, pois sua atividade básica é "CPU bound". Os chips CISC (Complex Instrution Set Computing) de aplicação mais geral - típicas de ambientes comerciais - não ofereciam a velocidade necessária aos trabalhos com extensas manipulação de números e visualização gráfica. Em ambiente comercial, por seu lado, é necessário considerar todo o conjunto que compõe o sistema, como CPU, memória, velocidade de discos, sistema operacional e software de aplicação. Uma comparação levando em conta apenas o processador e sua técnica, não é correta. Muitos dos fatores que aumentam a velocidade de um processador RISC, não são inerentes a esta tecnologia (como uso de cache, pipeline de instruções e grande número de registradores na CPU), sendo que estes recursos estão disponíveis a qualquer projeto de computador, sendo usados também em máquinas CISC. A análise dos processadores do mercado mostra que nos aspectos de mips ou operações aritméticas com números inteiros (SPEC Integer), o desempenho dos chips RISC e CISC são similares; em ponto flutuante (SPEC Floating Point) os RISC tendem a apresentar resultados melhores, embora o Pentium lhes seja equivalente. 3.2. A evolução da tecnologia conduz para a igualdade Cada vez mais as tecnologias RISC e CISC estão se aproximando: processadores RISC estão aumentando seu conjunto de instruções e os CISC estão adotando técnicas originalmente implementadas nos RISC. Por exemplo, o número de ciclos por instrução é bastante similar em ambos. O resultado prático é que o "path length" destes processadores para executar uma tarefa é praticamente igual. As razões para isso são simples. Em aplicações comerciais, a CPU trabalha cerca de 30% do tempo com instruções de movimentação de cadeias de caracteres de um lugar a outro na memória; em programação Cobol é interessante dispor de instruções de aritmética decimal e trabalhar com operandos "não alinhados". Desta forma, será necessário adicionar-se instruções tipicamente CISC para trabalhar adequadamente com estas tarefas. O Pentium, segundo a Intel, utiliza tecnologia CRISC, acoplamento das duas técnicas. Na verdade algumas máquinas RISC utilizam poucas de suas técnicas básicas, ao mesmo tempo que implementam conceitos típicos dos projetos CISC; entretanto, adotam o jargão RISC por questão de marketing. Um ambiente comercial é caracterizado por processamento transacional, com muita manipulação de cadeias e inteiros além de alto fluxo de entrada/saída. Já o científico se caracteriza por grande número de instruções de ponto flutuante. A conclusão lógica é que as máquinas dos dois tipos tem diferentes concepções para diferentes utilizações. 3.3. Oito diferenças críticas 1 2 3 4 5 6 7 8 Instruções simples levando um ciclo Apenas LOAD/STORE referenciam a memória Altamente pipelined Instruções executadas pelo hardware Instruções com formato fixo Poucas instruções e modos A complexidade está no compilador Múltiplos conjuntos de registradores Instruções complexas levando múltiplos ciclos Qualquer instrução pode referenciar a memória Não tem pipeline ou tem pouco Instruções interpretadas pelo microprograma Instruções com vários formatos Muitas instruções e modos de endereçamento A complexidade está no microprograma Conjunto único de registradores 4. Exercícios Resolvidos i. Historicamente, quais necessidades levaram a criação do RISC? e do CISC? Os primeiros computadores digitais eram extremamente simples, possuíam poucas instruções e somente um ou dois modos de endereçamento (detalhe: as instruções eram executadas diretamente pelo hardware). A mudança veio com a introdução da série IBM360. Todos os modelos desta série eram microprogramados. Os microprogramas executados apresentavam um conjunto de instruções no nível de máquina, chamada de linguagem de máquina (exemplo: ADD, MOVE, etc.). A grande evolução das linguagens de programação de alto nível acarretou no problema da distância semântica (semantic gap) entre estas linguagens e a linguagem de máquina, o que tornou a construção de compiladores uma tarefa bastante complexa. Na tentativa de reduzir a distância semântica, optou-se por elevar o nível da linguagem de máquina. Uma forma encontrada para realizar isto foi introduzir o conceito de microcódigo. O microcódigo é um software bastante primitivo, que controla diretamente os dispositivos de hardware. Este software é composto por um conjunto de microprogramas, usualmente armazenado em ROM, mais rápida do que a RAM. Ele é na verdade um interpretador que busca as instruções de máquina na memória principal gerando um conjunto de sinais de controle necessários para a execução de tais instruções pelo hardware. Para executar uma instrução ADD, por exemplo, o microprograma específico precisa determinar onde os valores a serem adicionados estão armazenados, buscá-los na memória, adicioná-los e armazenar o resultado em um registrador ou na própria memória. Desta forma, foram adicionadas novas instruções na linguagem de máquina, assim como outros modos de endereçamento. À medida que o nível da linguagem de máquina eleva-se (oferece mais instruções complexas), o seu interpretador torna-se maior, mais complexo e mais lento e, um maior número de instruções, implica em um maior tempo gasto na decodificação dos opcodes. Com o aumento da velocidade da RAM, diminuindo a diferença com a velocidade da ROM, e a crescente complexidade do microcódigo (devido à grande quantidade de microprogramas acrescentados) o desempenho desta arquitetura já não era tão extraordinário. Além disso, apesar da arquitetura CISC oferecer um grande conjunto de instruções e vários modos de endereçamento, apenas uma pequena porção destas instruções são realmente utilizados. O restante são utilizados eventualmente. Deste modo, constatouse que a velocidade da máquina era abreviada devido à adição de instruções e modos que raramente são utilizados. A partir disto, percebeu-se que era possível aumentar a velocidade de um computador eliminando-se o interpretador de instruções complexas, reduzindo o conjunto de instruções, eliminado todas as instruções que não podem ser completadas em um único ciclo da via de dados, com exceção de LOAD/STORE. Os programas de usuários, desta forma, são compilados para seqüências destas instruções e então executadas diretamente pelo hardware. APLICATIVOS S.0. LINGUAGEM DE MÁQUINA MICROCÓDIGO DISPOSITIVOS FÍSICOS ii. Um sistema de computador que utiliza o conceito de microcódigo. Este, serve de intermediário entre a linguagem de máquina e os dispositivos físicos, ou seja, serve como um interpretador. Qual o número máximo de instruções que um processador deve ter para ser considerado RISC? No princípio, a base da arquitetura RISC pretendia que esta possibilitasse instruções simples e que fossem as mais freqüentemente utilizadas, construindo um conjunto otimizado de instruções. Um aspecto importante era permitir a otimização da implementação de pipeline nesta arquitetura, não o número de instruções que ela disponibilizaria. Algumas operações exigem a implementação de operações que não podem ser feitas em um único ciclo, exigindo que a arquitetura RISC tivesse ao menos algumas instruções mais complexas, como por exemplo para lidar com gerenciamento de memória virtual, multiprocessamento, etc. A partir daí, várias máquinas lançadas no mercado vêm apresentando número variável de instruções, o que deu margem a várias informações desencontradas sobre o que vem a ser uma máquina RISC e uma máquina CISC. Por motivo de marketing muitas máquinas com características de funcionamento de arquiteturas CISC são denominadas RISC pelo simples fato de apresentarem menos de 100 instruções. O máximo que se pode conseguir, é basear este número em máquinas conhecidas: a Motorola 88000 apresenta 51 instruções, já a MIPS R4000 possui 94 instruções. Apesar de ter sido cogitado no principio a criação de uma máquina com uma única instrução [FALTOU REFERÊNCIA], parece que seguimos o caminho contrário, e as máquinas RISC aumentam seu número de instruções a cada versão. Segundo um popular jornal de computação [FALTOU REFERÊNCIA], processadores com menos que 100 instruções são considerados RISC. Porém não existe nenhuma restrição formal quanto ao número máximo de instruções de um processador RISC, desde que cada instrução seja executada em um ciclo da via de dados. O único problema real é que a complexidade do bloco decodificador, OP, cresce exponencialmente com o número de instruções e assim consome proporcionalmente crescentes áreas da pastilha. iii. Discorra sobre papel do compilador para um RISC (pode usar comparações como o CISC) FILOSOFIA DO PROJETO RISC: Deixar tanto trabalho quanto for possível a cargo do compilador. De certa forma, o trabalho para se alcançar os objetivos dos projetistas ao criar arquitetura RISC, simplificar o conjunto de instruções e otimizar o funcionamento do processador, recaiu sobre o compilador. Pelo fato de não mais se utilizar microcódigo para fazer a interpretação da linguagem de máquina o trabalho do compilador se tornou um pouco mais complicado. Agora, além de gerar microcódigo para programas de usuários, ele deve administrar toda a seqüência de pipeline do programa, e garantir seu funcionamento otimizado, de forma a não permitir ciclos vazios, além de possibilitar que operações com dados da memória possam ser feitas em média em um ciclo de via de dados. Logo, a tarefa executada pelo microcódigo da arquitetura CISC passa a ser do compilador. A tecnologia do compilador é um fator crítico do projeto RISC. Compiladores otimizados devem transportar toda a complexidade do hardware para a fase de compilação. Para manter a simplicidade do hardware, o compilador é projetado para lidar com características intrincadas da arquitetura RISC, como cargas, armazenamento e desvios atrasados, que aumentam consideravelmente a complexidade do compilador. O compilador deve fazer uso intensivo de registradores para reduzir o tráfego de memória, para evitar instruções que demoram mais do que um ciclo da via de dados para serem completados. Consequentemente, é essencial que o compilador RISC seja capaz de otimizar da melhor maneira possível o uso de registradores, levando em consideração que as instruções comuns não podem utilizar operandos de memória. Para viabilizar esta otimização são utilizados algoritmos de alocação de registradores sofisticados. Apesar destes algoritmos melhorarem o código colocando mais variáveis em um número limitado de registradores, eles também aumentam a complexidade do compilador. Além disso, o compilador RISC deve, sempre, buscar produzir um código correto e eficiente, tentando contornar os problemas acarretados pelos saltos atrasados, por exemplo. A partir destes fatos, constata-se que a arquitetura RISC também possui alguns problemas, assim como a arquitetura CISC, guardadas as devidas proporções. iv. Descreva a passagem de parâmetros entre procedimentos em uma máquina: a)CISC A maioria dos procedimentos recebem parâmetros fornecidos pelo procedimento que chama. Nas máquinas CISC, estes parâmetros são empilhados um de cada vez, pelo processo que chama, no início da execução da instrução CALL. O procedimento chamado, busca na pilha os parâmetros esperados. Este processo se baseia 100% na comunicação com a memória. b)RISC O objetivo de toda máquina RISC é executar uma instrução por ciclo, em média. Uma vez que LOAD e STORE requerem, normalmente, dois ciclos, quanto menos instruções deste tipo forem utilizados, maior será a “eficiência” da máquina. Grande parte do tráfego de memória total está relacionada a chamadas de procedimentos. Parâmetros têm que ser passados, registradores têm que ser salvos, e o endereço de retorno tem que ser empilhado na chamada e desempilhado no retorno. Todas estas ações geram alto tráfego de memória. Uma forma de eliminar parte deste tráfego é utilizar um método chamado janelas sobrepostas de registradores, adotado por algumas máquinas RISC. Quando janelas sobrepostas são utilizadas, a CPU contém um grande número de registradores, mas em cada momento somente um subconjunto deles, 32 registradores de 32 bits, é visível para um programa. Este conjunto é dividido em quatro grupos distintos com 8 registradores cada. O primeiro grupo (R0 a R7) guarda variáveis globais e ponteiros; esses são utilizados por todos os procedimentos ao longo do programa, sendo da responsabilidade decidir o que colocar em cada registrador. O segundo grupo, R8 a R15, guarda os parâmetros de entrada; evita o uso de pilha, que é utilizada somente se o número de parâmetros exceder 8, neste caso, o último registrador contém o endereço da pilha onde se encontra o restante dos parâmetros. Não colocar os parâmetros na pilha elimina STORE’s quando eles são passados e elimina LOAD’s quando eles são acessados pelo procedimento chamado. O terceiro grupo , R16 a R23, estão disponíveis para as variáveis locais, o excedente também vai para a pilha. O quarto grupo de registradores, R24 a R31, é utilizado para passar os parâmetros para os procedimentos chamados, sendo a pilha utilizada somente na falta de registradores para os parâmetros. Desta forma, quando um procedimento A chama um procedimento B, A deve passar os parâmetros para B, colocando-os nos registradores de parâmetros de saída. Os parâmetros de entrada de B serão os parâmetros de saída de A, sendo assim, os dois conjuntos trabalham sobrepostos (janelas sobrepostas). v. Debata sobre: CACHE x REGISTRADORES O conjunto de registradores, age com um pequeno e rápido buffer para o armazenamento de um subconjunto de todas as variáveis que provavelmente serão utilizadas. Deste ponto de vista, o conjunto de registradores se comporta como uma memória cache, porém existem diferenças entre estes meios de armazenamento. A cache tem a vantagem de armazenar não somente dados, como também instruções recentemente utilizadas, o que seria impossível para os registradores. Além disso ela não possui uma divisão rígida como a imposta pelo método de janelas sobrepostas. No RISC todas as variáveis locais e globais, são armazenadas em registradores. Estudos mostram que existe um bom número de variáveis globais, porém, nem todos são fortemente utilizadas. A cache, como guarda as mais recentemente utilizadas, com o tempo, terá a vantagem de guardar apenas aquelas mais necessárias. Constata-se, então, que a cache possui uma importante característica de se adaptar ao padrão de uso real, sendo potencialmente eficiente. Porém, toda entrada de cache possui três partes: o bit válido, o tag e o dado. Somente a última é utilizada para armazenar dados, e as duas primeiras são uma forma de overhead o que reduz a eficiência da máquina. Além disso, um registrador ser acessado basta conhecer o seu número e o da janela. Atividade simples e rápida. Já o acesso à memória cache, é uma tarefa complicada, pois são necessários realizar cálculos relativamente complexos para encontrar o endereço. A cache é realmente tão rápida quanto um registrador, porém, o acesso a ela é demasiadamente lento. REGISTRADORES CACHE VARIÁVEIS LOCAIS Todas as variáveis locais são Armazena as variáveis mais armazenadas recentemente utilizadas VARIÁVEIS GLOBAIS Todas são armazenadas ENDEREÇAMENTO DE MEMÓRIA Acesso direto a partir de seu Endereçamento complexo que número e o da janela deve ser calculado TRANSFERÊNCIA Impossível a transferência de Em blocos e permite a funções ou trechos de transferencia de trechos de programa programas/funções vi. Armazena somente as mais recentemente utilizada Uma Máquina RISC pode (ou deve) ter CACHE? Uma máquina RISC pode ter cache. Isto dependerá da aplicação para o qual a máquina é projetada. A cache oferece muitas características que podem ser benéficas para um projeto RISC, entre elas, a possibilidade de armazenamento de trechos de código, que é impossível para os registradores. Além disso, os “on-chip caches”, que quando cronometrados se apresentam tão rápidos quanto o processador. Conclui-se que a decisão correta, quanto a implementação ou não da memória cache, deve ser baseada numa ponderação sobre todas características debatidas no item anterior. vii. Quais os modos de endereçamento permitidos nas máquinas RISC? Os modos de endereçamento de máquinas RISC variam de máquina para máquina, dentre as máquinas mais conhecidas existe uma variação de 1 a 4 modos de endereçamento. Entretanto, devido para não quebrar a regra primordial da arquitetura RISC: não utilizar mais de um ciclo de via de dados, máquinas RISC só podem ter como operandos, registradores, logo, elas não permitem modo de endereçamento para endereçamento direto, endereçamento indexado, ou endereçamento indireto por registrador ou memória. Ou seja, referencias à memória devem ser feitas através de instruções especiais de LOAD e STORE adicionadas à arquitetura. Obviamente não é possível acessar à memória em um único ciclo, porém, a técnica de pipeline possibilita que um método de execução de instruções permita que no total n instruções sejam executadas em n ciclos, mantendo a média de uma instrução por ciclo de via de dados. O compilador do RISCII, entretanto trazia um JUMP condicional relativo ao ponteiro de instruções ou contador de programa, concatenando os três campos de baixa ordem poderia-se formar um offset de 19 bits com sinal podendo ser deslocado para cima ou para baixo. viii. Decodificar cada instrução o assembly da SPARC a) 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 1 1 1 0 0 0 1 1 1 0 0 1 0 1 0 1 0 1 1 1 1 0 0 0 0 0 1 1 1 1 0 1 rd rs1 op sisconst1a Verificando o primeiro campo da instrução (da esquerda para direita), poderia-se dizer que se trata de uma instrução para load ou store. Sendo o bit 13 igual a 1, sabemos que se trata de uma instrução de store, entretanto, como o campo op (bits 19 a 24) armazena uma seqüência de valores que não pertence a tabela de códigos de operação de load e store, é descartada esta possibilidade. O valor do primeiro campo também poderia indicar que a instrução seja uma instrução geral das máquinas SPARC, porém, o campo dos bits 19 a 24 também não fazem parte da tabela de instruções, podemos concluir que se trata de uma instrução inválida. b) 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 0 1 1 1 0 0 0 0 1 1 1 1 0 0 0 1 0 1 0 1 1 1 1 0 1 0 1 0 1 1 1 0 Os dois primeiros bits da instrução com valores 0 e 1 indicam uma instrução call. Não existem demais exigências para comprovação da instrução, os demais bits (0 a 29) formam o endereço de desvio e, desta forma, estão disponíveis para o contador do programa. c) 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 0 0 0 1 1 1 0 0 0 1 1 1 0 0 0 1 1 1 0 0 0 1 1 1 1 1 1 1 1 1 1 1 Devido aos valores da seqüência de bits 22 a 24 não corresponderem a nenhum valor da tabela de instruções de desvio condicional ou instruções sethi, descartou-se a possibilidade de serem este tipo de instrução e, considerando a seqüência dos bits do campo op (22 a 24) concluiu-se que se trata de uma instrução não implementada com código de campo correspondente a instruções ilegais. d) 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 1 0 1 0 1 0 1 0 1 0 1 0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 1 0 1 0 A instrução acima corresponde a instrução de subtração, caracterizado pelo valor 10 dos primeiros campos e pela seqüência de bits do campo op (22 a 24) que correspondem a instrução subcc. O campo rd (25 a 29) corresponde ao campo de registrador de destino, no caso o %R19, o campo rs corresponde ao registrador origem (14 a 18), um segundo operando origem pode ser um registrador (0 a 4) ou um valor constante (0 a 12). Na instrução acima, trata-se de um valor constante no campo siconst (1FOA h). e) 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 0 1 1 1 0 1 0 1 0 1 0 1 0 1 1 0 1 0 1 1 1 0 1 0 0 1 1 0 1 0 1 0 Esta é outra instrução call, foi analisada da mesma forma que a instrução da letra b. ix. a) Escreva o código assembly SPARC para os seguintes módulos if (delta > 0) z=0; else y=0; .text start: set delta, %r1 ld [%r1], %r2 set z, %r1 ld [%r1], %r3 set y, %r1 ld[%r1], %r4 cmp %r2, 0 bg cond mov %r0, %r4 cond: mov %r0, %r3 b) conta=0; while (conta <0){ y=conta; conta++; } ! delta é armazenado em %r2 ! z é armazenado em %r3 ! y é armazenado em %r4 ! compara delta e 0 ! se delta é maior que zero desvia para cond ! y recebe 0 ! instrução de desvio: .text start: set conta, %r0 ld [%r1], %r4 set y, %r0 ld [%r1], %r2 test: mov %r0, %r4 cmp %r4, 0 bg nop mov %r4, %r2 add %r4, 1, %r4 ba test ! conta é armazenada em %r4 ! y é armazenado e %r2 ! y=conta; ! compara conta e 0; 5. Bibliografia TANENBAUM, A., Organização e Arquitetura de computadores, Prentice Hall do Brasil, 1990 STALLINGS, W., Computer Organization and Architeture, Prentice Hall, 1996 5.1. Internet http://minerva.ufpel.tche.br/~machado/risccisc/pagina.html http://www.din.uem.br/sica/apostilas http://www.sunworld.com/sunworldonline/swol-09-1999/swol-09-insidesolaris.html