FATEC São José dos Campos – Professor Jessen Vidal
Disciplina: Arquitetura e Organização de Computadores
Docente: Leonidas Lopes de Melo
Processadores
Discentes: Beatriz Carvalho Cunha
Beatriz Sthefanny Araujo Santos
Clarice Corrêa Costa
Larissa Aparecida Claro
Luiz Ricardo Silva
Mariana Yasmin Santos de Oliveira
Rafael Slivka
Tiago Bernardo Santos
Thiago da Silva de Abreu
São José dos Campos – SP
Maio de 2024
1 Introdução
Um computador digital consiste em um sistema interconectado de processadores, memória e
dispositivos de entrada/saída. A CPU (Unidade Central de Processamento) é como o "cérebro" do
computador. Sua principal função é executar os programas armazenados na memória principal,
seguindo suas instruções em sequência. Os componentes são conectados através de um conjunto de
fios paralelos, chamado barramento, que transmite endereços, dados e sinais de controle.
2 Organização da CPU
A CPU é composta por uma série de registradores (geralmente entre 1 e 32), a ULA
(Unidade Lógica e Aritmética) e diversos barramentos que conectam essas partes. Durante as
operações de cálculo, os registradores alimentam dados para os dois registradores de entrada da
ULA. A ULA realiza operações como adição, subtração e outras sobre esses dados, gerando um
resultado que pode ser armazenado em um registrador.
As instruções da CPU podem ser classificadas em duas categorias: registrador-memória e
registrador-registrador. As instruções registrador-memória permitem que dados da memória sejam
carregados nos registradores, enquanto as instruções registrador-registrador envolvem operações
entre dados nos registradores. O ciclo do caminho de dados, que compreende passar operandos pela
ULA e armazenar o resultado, é fundamental para o funcionamento da CPU e influencia
diretamente em sua velocidade de processamento.
3 Execução de instrução
A CPU executa cada instrução em uma série de pequenas etapas
As etapas consistem em:
1. Trazer a próxima instrução da memória até o registrador de instrução.
2. Alterar o contador de programa para que aponte para a próxima instrução.
3. Determinar o tipo de instrução trazida.
4. Se a instrução usar uma palavra na memória, determinar onde essa palavra está.
5. Trazer a palavra para dentro de um registrador da CPU, se necessário.
6. Executar a instrução.
7. Voltar à etapa 1 para iniciar a execução da instrução seguinte.
Essa sequência é denominada ciclo buscar-decodificar-executar e é fundamental para a operação do
computador
A equivalência entre processadores de hardware e interpretadores tem importantes implicações para
a organização e computadores e para o projeto de sistemas de computadores.
Após a especificação da linguagem de máquina "L" para um novo computador, a equipe pode
decidir se quer construir um processador de hardware para executar o programa em "L" diretamente
ou se quer escrever um interpretador para interpretar programas em L. São possíveis ainda
construções híbridas (com um pouco de execução em hardware, bem como alguma interpretação de
software).
Um interpretador subdivide as instruções da máquina em questão em pequenas etapas. Os primeiros
computadores tinham conjuntos de instruções pequenos, simples. Mas a procura por equipamentos
mais poderosos levou, entre outras coisas, a instruções individuais mais poderosas.
Logo se descobriu que instruções mais complexas muitas vezes levavam à execução mais rápida do
programa mesmo que as instruções individuais demorassem mais para ser executadas. As instruções
mais complexas eram melhores porque a execução de operações individuais às vezes podia ser
sobreposta ou então executada em paralelo usando hardware diferente.
A IBM introduziu em 1950 o termo arquitetura, que é nada mesmo uma única família de máquinas,
todas executando as mesmas instruções.
De acordo com que as capacidades de computação cresciam depressa, a demanda por máquinas de
baixo custo favoreciam projetos de computadores que usassem interpretadores. A capacidade de
ajustar hardware e interpretador para um determinado conjunto de instruções surgiu como um
projeto muito eficiente em custo para processadores.
No final da década de 1970, a utilização de processadores simples que executavam interpretadores
tinha se propagado em grande escala. A utilização de um interpretador eliminava as limitações de
custo inerentes às instruções complexas, de modo que os projetistas começaram a explorar
instruções muito mais complexas, em particular os modos de especificar os operandos a utilizar.
Durante esse período, um dos maiores desafios enfrentados pelos projetistas de microprocessadores
era lidar com a crescente complexidade, possibilitada por meio de circuitos integrados. Uma
importante vantagem do método baseado em interpretador era a capacidade de projetar um
processador simples e confinar quase toda a complexidade na memória que continha o
interpretador. Assim, um projeto complexo de hardware se transformou em um projeto complexo de
software.
4 RISC x CISC
No final da década de 1970, surgiram esforços para criar processadores com instruções
complexas, mas John Cocke da IBM liderou um grupo que propôs uma abordagem diferente,
desenvolvendo o conceito de Reduced Instruction Set Computer (RISC). Paralelamente, em
Berkeley e Stanford, foram projetados chips baseados nessa ideia. Os processadores RISC tinham
um número menor de instruções, visando a execução rápida das mesmas. Isso levou a uma
rivalidade com a abordagem tradicional Complex Instruction Set Computer (CISC), representada
por sistemas como o VAX da DEC e os mainframes da IBM.
Os defensores do RISC argumentavam que, embora suas instruções fossem mais simples, a
execução rápida delas compensava a necessidade de mais instruções para realizar uma tarefa. No
entanto, apesar das vantagens de desempenho dos processadores RISC, a indústria não abandonou
completamente os CISC devido a questões de compatibilidade de software e a habilidade da Intel de
adaptar elementos RISC em suas arquiteturas CISC. Isso resultou em uma abordagem híbrida que
mantinha o desempenho competitivo, permitindo a execução de software legado.
Os processadores RISC e CISC representam abordagens distintas para o projeto de
arquiteturas de processadores. Enquanto o RISC enfatiza instruções simples e rápidas, o CISC
busca oferecer um conjunto mais amplo e complexo de instruções. Essa diferença de abordagem
levou a uma disputa no desenvolvimento de processadores, com cada lado argumentando a favor de
suas vantagens. Apesar das vantagens de desempenho dos processadores RISC, os sistemas CISC
continuaram a ser amplamente utilizados devido à sua compatibilidade com o software existente e à
capacidade de lidar com uma variedade mais ampla de tarefas computacionais. A introdução de
técnicas híbridas, incorporando elementos de ambas as arquiteturas, resultou em uma evolução
contínua da tecnologia de processadores, atendendo às demandas cada vez mais complexas das
aplicações computacionais modernas.
5 Princípios de projeto para computadores modernos
Os princípios de projeto para computadores modernos, inspirados pela abordagem RISC,
abrangem diversas áreas fundamentais da arquitetura de computadores. Eles refletem um conjunto
de diretrizes que os projetistas de CPUs de uso geral se esforçam para seguir, visando otimizar o
desempenho, a eficiência e a compatibilidade dos sistemas computacionais. Esses princípios
incluem a execução direta das instruções pelo hardware, a maximização da taxa de execução das
instruções, a facilidade de decodificação das instruções, a limitação do acesso direto à memória
apenas para instruções de carga e armazenamento, e a provisão de muitos registradores para
minimizar a dependência da memória. Essas diretrizes refletem uma abordagem centrada na
eficiência e no desempenho dos computadores modernos, buscando explorar ao máximo o potencial
das arquiteturas de processadores para atender às demandas cada vez mais exigentes das aplicações
computacionais.
6 Paralelismo no nível de instrução
Para o nível de instrução, temos que o paralelismo é explorado nas instruções individuais
para aumentar o desempenho da máquina, assim permitindo a execução de múltiplas partes de uma
instrução simultaneamente. Um grande exemplo disso é o conceito de pipelining, onde se existe a
execução da instrução que acaba sendo dividida em várias etapas, podendo então ser executadas em
paralelo, reduzindo assim os gargalos de desempenho associados à busca e execução de instruções
na memória.
Desde os primeiros computadores, como o IBM Stretch, este conceito foi aplicado para que
ocorresse a melhoria da eficiência na execução de instruções. Sendo assim o paralelismo no nível de
instrução é exemplificado pelo conceito de pipelining, que acaba por ser essencial para otimizar o
desempenho dos computadores, tornando a executar tarefas de maneira mais eficiente e rápida.
Em resumo, enquanto o paralelismo no nível do processador envolve várias unidades de
processamento, o paralelismo no nível de instrução opera dentro de uma única unidade de
processamento, otimizando a execução de instruções individuais.
6.1 Pipelining
Pipelining é uma técnica de processamento que permite que várias instruções sejam
executadas de forma sobreposta em diferentes estágios. Isso compromete entre latência, o tempo
que uma instrução leva para ser executada, e a largura de banda do processador, medida em MIPS.
Com um ciclo de tempo de T ns e n estágios no pipeline, a latência é de nT ns, já que cada instrução
passa por n estágios, cada um levando T ns para ser concluído.
6.2 Computadores Paralelos
Computadores paralelos são sistemas que executam várias tarefas simultaneamente,
distribuindo a carga de trabalho entre múltiplos processadores. Eles são altamente eficientes para
lidar com programas altamente regulares, como aqueles envolvendo laços e matrizes. Existem dois
principais métodos de execução eficaz desses programas: processadores SIMD e processadores
vetoriais. Os processadores paralelos de dados, como os baseados em SIMD, executam a mesma
instrução em vários conjuntos de dados, maximizando a utilização do silício e economizando
espaço, tornando-os ideais para programas altamente paralelos e regulares.
6.2 Arquitetura Superescalares
Arquiteturas superescalares permitem que os processadores emitam várias instruções em um
único ciclo de clock, utilizando múltiplas unidades funcionais. Apesar de terem um único pipeline,
a execução simultânea de várias instruções resulta em uma taxa final de execução muito mais alta
do que a taxa de emissão. Isso maximiza a utilização dos recursos do processador, distribuindo
eficientemente a carga de trabalho entre as unidades funcionais disponíveis, contribuindo para um
aumento significativo do desempenho ao executar um número maior de instruções em paralelo.
7 Paralelismo no nível do processador
No nível do processador, o paralelismo deve ocorrer entre várias CPUs, oferecendo ganhos
substanciais de desempenho, possibilitando assim os aumentos significativos na velocidade de
processamento. Por que ao distribuir e executar tarefas de forma simultânea entre diferentes
unidades de processamento, com esta abordagem é possível enfrentar desafios computacionais cada
vez mais complexos.
Com isso, é possível atender à demanda por computadores mais eficientes, capazes de
realizar desde simulações científicas avançadas até aplicações de entretenimento de multimídia em
tempo real. Podendo assim se resumir que o paralelismo no nível do processador tornou-se, assim,
uma evolução da computação moderna, impulsionando a inovação e possibilitando avanços em
diversas áreas da ciência e da tecnologia.
7.1 Multiprocessadores
Multiprocessadores são sistemas com mais de uma CPU que compartilham uma memória
em comum, similar a um grupo de pessoas dentro de uma sala de aula compartilhando um quadro.
Cada CPU pode ler ou escrever em qualquer parte da memória, exigindo coordenação em software
para evitar conflitos. Eles são fortemente acoplados quando as CPUs interagem de perto. Uma
implementação comum é um barramento único conectando várias CPUs e uma memória.
Multiprocessadores facilitam o trabalho com o modelo de programação de uma única memória
compartilhada, permitindo que várias CPUs executem tarefas simultaneamente e coordenem
atividades, como o processamento de dados em paralelo, como o exemplo da busca de células
cancerosas em uma imagem digitalizada.
7.2 Multicomputadores
As CPUs de um multicomputador se comunicam enviando mensagens umas às outras, mais
ou menos como enviar e-mails, porém, com muito mais rapidez. Em sistemas grandes, não é prático
ter cada computador ligado a todos os outros, portanto, são usadas topologias como malhas 2D e
3D, árvores e anéis. O resultado é que mensagens de um computador para outro muitas vezes
passam por um ou mais computadores ou comutadores (chaves) intermediários para ir da fonte até o
destino. Não obstante, podem-se conseguir tempos de transmissão de mensagem da ordem de
alguns microssegundos sem muita dificuldade. Multicomputadores com mais de 250 mil CPUs,
como o Blue Gene/P da IBM, já foram construídos.
FATEC São José dos Campos – Professor Jessen Vidal
Disciplina: Arquitetura e Organização de Computadores
Docente: Leonidas Lopes de Melo
Nível da microarquitetura
Discentes: Beatriz Carvalho Cunha
Beatriz Sthefanny Araujo Santos
Clarice Corrêa Costa
Larissa Claro
Luiz Ricardo Silva
Mariana Yasmin Santos de Oliveira
Rafael Slivka
Tiago Bernardo Santos
Thiago da Silva de Abreu
São José dos Campos – SP
Maio de 2024
4.1 Um exemplo de microarquitetura
4.1.1 O caminho de dados
O caminho de dados é a parte da CPU que contém a ULA, suas entradas e suas saídas. O caminho
de dados de nossa microarquitetura de exemplo é mostrado na Figura 4.1. Embora tenha sido
cuidadosamente otimizado para interpretar programas IJVM, ele guarda uma razoável semelhança
com o caminho de dados usado na maioria das máquinas. Contém vários registradores de 32 bits,
aos quais atribuímos nomes simbólicos como PC, SP e MDR. Embora alguns desses nomes sejam
familiares, é importante entender que esses registradores são acessíveis apenas no nível de
microarquitetura (pelo microprograma). Eles recebem esses nomes porque em geral contêm um
valor correspondente à variável do mesmo nome na arquitetura do nível de ISA. A maior parte dos
registradores pode dirigir seu conteúdo para o barramento B. A saída da ULA comanda o
deslocador e em seguida o barramento C, cujo valor pode ser escrito em um ou mais registradores
ao mesmo tempo. Por enquanto, não há nenhum barramento A; incluiremos um mais adiante.
A ULA é idêntica à mostrada nas figuras 3.18 e 3.19. Sua função é determinada por seis linhas de
controle. O segmento de reta diagonal com rótulo “6” na Figura 4.1 indica que há seis linhas de
controle de ULA, a saber: F0 e Fj para determinar a operação da ULA; ENA e ENB para habilitar
as entradas individualmente; INVA para inverter a entrada esquerda e INC para forçar um vai-um
para o bit de ordem baixa, somando 1 ao resultado. Contudo, nem todas as 64 combinações de
linhas de controle de ULA fazem algo de útil.
Algumas das combinações mais interessantes são mostradas na Figura 4.2. Nem todas essas funções
são necessárias para a IJVM, mas, para a JVM completa, muitas delas viriam a calhar. Em muitos
casos, há várias possibilidades de conseguir o mesmo resultado. Nessa tabela, + significa “mais”
aritmético e - significa “menos” aritmético; assim, por exemplo, -A significa o complemento de dois
de A.
A ULA da Figura precisa de duas entradas de dados: uma entrada esquerda (A) e uma entrada
direita (B). Ligado à entrada esquerda está um registrador de retenção, H. Ligado à entrada direita
está o barramento B, que pode ser carregado por cada uma de nove fontes, indicadas pelas nove
setas cinza que chegam até ele.
H pode ser carregado com a escolha de uma função da ULA que passe diretamente da entrada
direita (vinda do barramento B) para a saída da ULA. Uma função desse tipo seria somar as
entradas da ULA, porém, com ENA negado, de modo que a entrada esquerda é forçada a zero.
Adicionar zero ao valor no barramento B resulta somente no valor no barramento B. Então, esse
resultado pode ser passado pelo deslocador sem modificação e armazenado em H.
Além das funções citadas, duas outras linhas de controle podem ser usadas independentemente para
controlar a saída da ULA. SLL8 (Shift Left Logical) desloca o conteúdo para a esquerda por 1 byte,
preenchendo os 8 bits menos significativos com zeros. SRA1 (Shift Right Arithmetic) desloca o
conteúdo para a direita por 1 bit, deixando inalterado o bit mais significativo.
Combinações úteis de sinais da ULA e a função executada.
• Temporização do caminho de dados
A temporização desses eventos é mostrada na Figura. Um pulso curto é produzido no início de cada
ciclo de clock. Na borda descendente do pulso, os bits que comandarão todas as portas são
ajustados, o que leva um tempo finito e conhecido, Aw. Depois, o registrador necessário no
barramento B é selecionado e conduzido até este. Demora Ax para o valor ficar estável. Então, a
ULA e o deslocador começam a operar com dados válidos. Após outro Ay, as saídas da ULA e do
deslocador estão estáveis. Após um Az adicional, os resultados se propagaram ao longo do
barramento C até os registradores, onde podem ser carregados na borda ascendente do próximo
pulso. A carga deve ser acionada pela borda ascendente do próximo pulso e de forma rápida, de
modo que, se alguns dos registradores de entrada forem alterados, o efeito não será sentido no
barramento C até muito tempo após os registradores terem sido carregados.
Um modo um pouco diferente de ver o ciclo de caminho de dados é imaginá-lo fragmentado em
subciclos implícitos. O início do subciclo 1 é acionado pela borda descendente do clock. As
atividades que ocorrem durante os subciclos são mostradas a seguir, junto com as extensões dos
subciclos (entre parênteses).
1. Os sinais de controle são ajustados (Aw).
2. Os registradores são carregados no barramento B (Ax).
3. Operação da ULA e deslocador (Ay).
4. Os resultados se propagam ao longo do barramento C de volta aos registradores (Az).
O intervalo de tempo após Az oferece alguma tolerância, pois os tempos não são exatos. Na borda
ascendente do próximo ciclo de clock, os resultados são armazenados nos registradores.
• Operação de memória
Nossa máquina tem dois modos diferentes de se comunicar com a memória: uma porta de memória
de 32 bits, endereçável por palavra, e outra de 8 bits, endereçável por byte. A porta de 32 bits é
controlada por dois registradores, MAR (Memory Address Register - registrador de endereço de
memória) e MDR (Memory Data Register - registrador de dados de memória), como mostra a
Figura 4.1. A porta de 8 bits é controlada por um registrador, PC, que lê 1 byte para os 8 bits de
ordem baixa do MBR. Essa porta só pode ler dados da memória; ela não pode escrever dados na
memória.
Cada um desses registradores (e todos os outros na Figura 4.1) é comandado por um ou dois sinais
de controle. Uma seta clara sob um registrador indica um sinal de controle que habilita a saída do
registrador para o barramento B. Visto que MAR não tem conexão com o barramento B, não tem
sinal de habilitação. H também não tem esse sinal porque está sempre habilitado, por ser a única
entrada esquerda possível da ULA.
Uma seta negra sob um registrador indica um sinal de controle que escreve (isto é, carrega) o
registrador a partir do barramento C. Uma vez que MBR não pode ser carregado a partir do
barramento C, não tem um sinal de escrita (embora tenha dois outros sinais de habilitação, descritos
mais adiante). Para iniciar uma leitura ou escrita da memória, os registradores de memória
adequados devem ser carregados e em seguida deve ser emitido um sinal de leitura ou escrita para a
memória (não mostrado na Figura 4.1).
MAR contém endereços de palavras, de modo que os valores 0, 1, 2 etc. se referem a palavras
consecutivas. PC contém endereços de bytes, portanto, os valores 0, 1, 2 etc. se referem a bytes
consecutivos. Assim, colocar um 2 em PC e iniciar uma leitura de memória lerá o byte 2 da
memória e o colocará nos 8 bits de ordem baixa do MBR. Colocar 2 em MAR e iniciar uma leitura
de memória lerá os bytes 8-11 (isto é, palavra 2) da memória e os colocará em MDR.
Essa diferença de funcionalidade é necessária porque MAR e PC serão usados para referenciar duas
partes diferentes da memória. A necessidade dessa distinção ficará mais clara adiante. Por enquanto,
basta dizer que a combinação MAR/MDR é usada para ler e escrever palavras de dados de nível
ISA e a combinação PC/MBR é empregada para ler o programa executável de nível ISA, que
consiste em uma sequência de bytes. Todos os outros registradores que contêm endereços usam
endereço de palavras, como o MAR.
Na implementação física propriamente dita, há apenas uma memória real que funciona com bytes.
Permitir que MAR conte palavras (isso é necessário por causa do modo como a JVM é definida)
enquanto a memória física conta bytes depende de um truque simples. Quando o MAR é colocado
no barramento de endereço, seus 32 bits não são mapeados diretamente para as 32 linhas de
endereço, 0-31. Em vez disso, o bit 0 do MAR é ligado à linha 2 do barramento de endereço, o bit 1
do MAR é ligado à linha 3 do barramento de endereço e assim por diante. Os 2 bits superiores do
MAR são descartados, visto que só são necessários para endereços de palavra acima de 232,
nenhum dos quais é válido para nossa máquina de 4 GB. Usando esse mapeamento, quando MAR é
1, o endereço 4 é colocado no barramento; quando MAR é 2, o endereço 8 é colocado no
barramento e assim por diante. Esse estratagema está ilustrado na Figura 4.4.
Como já mencionamos, dados lidos da memória por uma porta de memória de 8 bits são devolvidos
em MBR, um registrador de 8 bits. MBR pode ser copiado (gated) para o barramento B por um
entre dois modos: com ou sem sinal. Quando é preciso o valor sem sinal, a palavra de 32 bits
colocada no barramento B contém o valor MBR nos 8 bits de ordem baixa e zeros nos 24 bits
superiores. Valores sem sinal são úteis para indexar em uma tabela ou quando um inteiro de 16 bits
tem de ser montado a partir de 2 bytes consecutivos (sem sinal) na sequência de instrução.
A outra opção para converter o MBR de 8 bits em uma palavra de 32 bits é tratá-lo como um valor
com sinal entre -128 e +127 e usar esse valor para gerar uma palavra de 32 bits com o mesmo valor
numérico. Essa conversão é feita duplicando o bit de sinal do MBR (o bit mais à esquerda) nas 24
posições superiores de bits do barramento B, um processo denominado extensão de sinal. Quando
essa opção é escolhida, os 24 bits superiores serão todos Os ou todos 1s, dependendo do bit mais à
esquerda do MBR de 8 bits ser um 0 ou um 1.
Mapeamento dos bits em MAR para o barramento de endereço
A opção de converter o MBR de 8 bits em um valor de 32 bits com sinal ou sem sinal no
barramento B é determinada por qual dos dois sinais de controle (setas claras sob MBR na Figura
4.1) for ativado. A necessidade dessas duas opções é a razão de haver duas setas presentes. A
capacidade de fazer o MBR de 8 bits agir como uma fonte de 32 bits para o barramento B é
indicada pelo retângulo tracejado na figura.
4.1.2 Microinstruções
Para controlar o caminho de dados precisamos de 29 sinais, que podem ser divididos em cinco
grupos funcionais, como descreveremos a seguir:
• 9 sinais para controlar escrita de dados do barramento C para registradores.
• 9 sinais para controlar habilitação de registradores dirigidos ao barramento B para a entrada da
ULA.
• 8 sinais para controlar as funções da ULA e do deslocador.
• 2 sinais (não mostrados) para indicar leitura/escrita na memória via MAR/MDR.
• 1 sinal (não mostrado) para indicar busca na memória via PC/MBR.
Os valores desses 29 sinais de controle especificam as operações para um ciclo do caminho de
dados. Um ciclo consiste em copiar valores dos registradores para o barramento B, propagar os
sinais pela ULA e pelo deslocador, dirigi-los ao barramento C e, por fim, escrever os resultados no
registrador ou registradores adequados. Além disso, se um sinal de leitura de dados da memória for
ativado, a operação de memória é iniciada no final do ciclo de caminho de dados, após o MAR ter
sido carregado. Os dados da memória estão disponíveis no final do ciclo seguinte em MBR ou
MDR e podem ser usados no ciclo que vem depois daquele. Em outras palavras, uma leitura de
memória em qualquer porta iniciada no final do ciclo k entrega dados que não podem ser usados no
ciclo k + 1, porém, somente no ciclo k + 2 ou mais tarde.
Nesse ponto, podemos controlar o caminho de dados com 9 + 4 + 8 + 2 + 1 = 24 sinais, daí 24 bits.
Contudo, esses 24 bits só controlam o caminho de dados por um ciclo. A segunda parte do controle
é determinar o que fazer no ciclo seguinte. Para incluir isso no projeto do controlador, criaremos um
formato para descrever as operações a serem realizadas usando os 24 bits de controle mais dois
campos adicionais: NEXT_ADDRESS e JAM. O conteúdo de cada um desses campos será
discutido em breve. A Figura 4.5 mostra um formato possível, dividido em seis grupos (listados
abaixo da instrução) e contendo os seguintes 36 sinais:
Addr - Contém o endereço de uma microinstrução potencial seguinte.
JAM - Determina como a próxima microinstrução é selecionada.
ULA - Funções da ULA e do deslocador.
C - Seleciona quais registradores são escritos a partir do barramento C.
Mem - Funções de memória.
B - Seleciona a fonte do barramento B; é codificado como mostrado.
A ordem dos grupos é, em princípio, arbitrária, embora na verdade a tenhamos escolhido com muito
cuidado para minimizar cruzamentos de linhas na Figura 4.6. Cruzamentos de linhas em diagramas
esquemáticos como essa figura costumam corresponder a cruzamento de fios em chips, o que causa
problemas em projetos bidimensionais, portanto, é melhor minimizá-los.
Formado da microinstrução para a Mic-1.
4.2 Exemplo de ISA: IJVM (pág. 201)
4.2.1 Pilhas (pág. 201)
● Linguagens de programação utilizam procedimentos (métodos) que possuem variáveis
locais.
● Variáveis locais são acessíveis apenas dentro do procedimento e deixam de ser acessíveis
quando o procedimento termina.
● A solução mais simples seria atribuir endereços de memória absolutos às variáveis, mas isso
não funciona quando um procedimento chama a si mesmo.
● Uma área da memória chamada pilha é reservada para as variáveis locais dos
procedimentos.
● Um registrador, como LV, aponta para a base das variáveis locais do procedimento ativo.
● Outro registrador, como SP, aponta para o topo da pilha das variáveis locais do
procedimento ativo.
● As variáveis são referenciadas pelo seu deslocamento em relação a LV.
● Quando um procedimento chama outro, LV é ajustado para apontar para as variáveis locais
do novo procedimento.
● LV sempre aponta para a base do quadro da pilha do procedimento ativo e SP para o topo.
● A memória só é alocada para os procedimentos ativos no momento, liberando-a quando um
procedimento retorna.
● Além de armazenar variáveis locais, as pilhas podem ser usadas para reter operandos
durante cálculos aritméticos, chamadas pilhas de operandos.
● Os quadros de variáveis locais e as pilhas de operandos podem ser combinados.
● Nem todas as máquinas utilizam uma pilha de operandos para efetuar aritmética, mas a JVM
e a IJVM trabalham dessa forma.
4.2.2 Modelo de memória IJVM (pág. 203)
● A Java Virtual Machine (JVM) é uma memória que pode ser vista como um arranjo de 4
GB ou [Link] palavras de 4 bytes cada.
● Diferente da maioria das Instruction Set Architectures (ISAs), a JVM não utiliza endereços
absolutos de memória visíveis diretamente no nível ISA.
● As instruções IJVM acessam a memória indexando a partir de ponteiros implícitos.
● As áreas de memória definidas em qualquer instante são:
1. Conjunto de constantes: Contém constantes, cadeias e ponteiros para outras áreas de
memória. Carregado na memória quando o programa é iniciado e não é alterado depois. O
registrador implícito CPP contém o endereço da primeira palavra deste conjunto.
2. Quadro de variáveis locais: Alocado para cada invocação de método para armazenar
variáveis durante a invocação. Inclui parâmetros passados na chamada do método. O
registrador implícito LV contém o endereço da primeira localização deste quadro.
3. Pilha de operandos: Espaço alocado diretamente acima do quadro de variáveis locais
para operandos. O registrador implícito SP contém o endereço da palavra do topo da pilha.
4. Área do método: Contém o programa. O registrador implícito PC contém o endereço da
próxima instrução a ser buscada.
● Os registradores CPP, LV e SP são ponteiros para palavras, deslocados pelo número de
palavras.
● Para o subconjunto de inteiros, todas as referências são em palavras, e todos os
deslocamentos usados para indexar esses quadros são deslocamentos de palavras.
● O endereço PC contém um endereço de byte, e uma adição ou subtração ao PC altera o
endereço por um número de bytes, não palavras.
● A largura da porta de memória para PC é de apenas 1 byte.
● Incrementar o PC por 1 e iniciar uma leitura resulta em uma busca pelo próximo byte.
● Incrementar o SP por 1 e iniciar uma leitura resulta em uma busca pela próxima palavra.
4.2.3 Conjunto de instruções da IJVM (pág. 204)
Cada instrução consiste em um opcode e às vezes um operando, tal como um deslocamento de
memória ou uma constante.
● A primeira coluna dá a codificação hexadecimal da instrução.
● A segunda dá seu mnemônico em linguagem de montagem.
● A terceira dá uma breve descrição de seu efeito.
4.2.4 Compilando Java para a IJVM (pág. 208)
Em (a), mostra um fragmento simples de código Java. Quando alimentado em um compilador Java,
este provavelmente produziria a linguagem de montagem IJVM mostrada em (b). Os números de
linhas de 1 a 15 à esquerda do programa de linguagem de montagem não fazem parte da saída do
compilador, o mesmo vale para os comentários (que começam com //). Eles estão ali para ajudar a
explicar a figura seguinte. Então, o assembler Java traduziria o programa de montagem para o
programa binário mostrado em (c).
4.3 Ilustração da Implementação (pág. 209)
Agora surge a questão da implementação. Em resumo, como é o funcionamento de um programa
que está operando na primeira e interpretando a última
4.3.1 Microinstrucões e notação(pág. 209)
● Introdução da notação MAL (Microlinguagem Assembly de alto nível) para descrever as
microinstruções.
● Descrição dos benefícios de uma notação simbólica em comparação com a linguagem
binária.
● Explicação da importância de controlar o ciclo por ciclo para análise e verificação das
operações.
● Apresentação da estrutura básica das microinstruções, que descrevem atividades em um
único ciclo de clock.
● Uso de atribuições simples, semelhantes a declarações em Java, para indicar operações a
serem executadas.
● Ampliação da notação para permitir múltiplas atribuições em uma única linha de
microinstrução.
● Distinção entre operações de leitura e escrita de memória de palavras de dados de 4 bytes e
operações de leitura de opcodes de 1 byte.
● Introdução de notação para desvios incondicionais e condicionais, incluindo o uso dos
registradores imaginários N e Z para teste de condições.
● Utilização de declarações combinadas para desvios condicionais.
● Notação para uso do bit JMPC para desvios condicionais baseados no conteúdo do
registrador MBR.
● Observações sobre a extensão de sinal e a escolha do valor da próxima microinstrução.
● Introdução da notação MAL (Microlinguagem Assembly de alto nível) para descrever as
microinstruções.
● Descrição dos benefícios de uma notação simbólica em comparação com a linguagem
binária.
● Explicação da importância de controlar o ciclo por ciclo para análise e verificação das
operações.
● Apresentação da estrutura básica das microinstruções, que descrevem atividades em um
único ciclo de clock.
● Uso de atribuições simples, semelhantes a declarações em Java, para indicar operações a
serem executadas.
● Ampliação da notação para permitir múltiplas atribuições em uma única linha de
microinstrução.
● Distinção entre operações de leitura e escrita de memória de palavras de dados de 4 bytes e
operações de leitura de opcodes de 1 byte.
● Introdução de notação para desvios incondicionais e condicionais, incluindo o uso dos
registradores imaginários N e Z para teste de condições.
● Utilização de declarações combinadas para desvios condicionais.
● Notação para uso do bit JMPC para desvios condicionais baseados no conteúdo do
registrador MBR.
● Observações sobre a extensão de sinal e a escolha do valor da próxima microinstrução.
● Introdução da notação MAL (Microlinguagem Assembly de alto nível) para descrever as
microinstruções.
● Descrição dos benefícios de uma notação simbólica em comparação com a linguagem
binária.
● Explicação da importância de controlar o ciclo por ciclo para análise e verificação das
operações.
● Apresentação da estrutura básica das microinstruções, que descrevem atividades em um
único ciclo de clock.
● Uso de atribuições simples, semelhantes a declarações em Java, para indicar operações a
serem executadas.
● Ampliação da notação para permitir múltiplas atribuições em uma única linha de
microinstrução.
● Distinção entre operações de leitura e escrita de memória de palavras de dados de 4 bytes e
operações de leitura de opcodes de 1 byte.
● Introdução de notação para desvios incondicionais e condicionais, incluindo o uso dos
registradores imaginários N e Z para teste de condições.
● Utilização de declarações combinadas para desvios condicionais.
● Notação para uso do bit JMPC para desvios condicionais baseados no conteúdo do
registrador MBR.
● Observações sobre a extensão de sinal e a escolha do valor da próxima microinstrução.
4.3.2 Implementação de IJVM que usa a Mic-1 (pág. 212)
O microprograma que executa na Mic-1 e interpreta a IJVM está representado na Figura 4.17.
Surpreendentemente, é bastante curto, com apenas 112 microinstruções no total. Cada
microinstrução possui três colunas: um rótulo simbólico, o código da microinstrução e um
comentário. É importante notar que as microinstruções consecutivas não precisam estar em
endereços sequenciais no armazenamento de controle, como já mencionado anteriormente.
4.4 Projeto do nível de microarquitetura (pág. 222)
Nessa parte, discutimos o processo de projeto da micro arquitetura de computadores, que requer
tomada de decisões considerando compromissos entre diversas características desejáveis. Em suma,
destaca que o projeto envolve fazer escolhas ponderadas entre diferentes aspectos, sendo o
compromisso central entre velocidade e custo. Propõe uma análise minuciosa desse compromisso
para compreender melhor suas implicações em desempenho, custo e complexidade do hardware.
4.4.1 Velocidade Versus Custo (pág. 223)
Nessa parte, exploramos estratégias para aumentar a velocidade de execução de um computador e
os custos associados a essas melhorias. Destacam-se três abordagens principais: reduzir ciclos de
clock, simplificar organização e sobrepor execução de instruções. Exemplos ilustram como
codificação e decodificação afetam o ciclo de clock, e a sobreposição de instruções é vista como a
técnica mais eficaz. O texto também aborda o custo em termos de área de circuito e destaca o trade-
off entre velocidade e custo ao reduzir o armazenamento de controle. As 3 abordagens principais:
· Redução de ciclos de clock: Isso pode ser alcançado otimizando o hardware para realizar
operações mais rapidamente. Por exemplo, ao adicionar um circuito especializado para
incrementar o contador de programa (PC), podemos eliminar ciclos de clock anteriormente
necessários para essa operação.
· Simplificação da organização: Simplificar o design do hardware pode reduzir o tempo
necessário para cada ciclo de clock. Por exemplo, separar a busca e a execução de instruções
permite que a unidade de busca busque a próxima instrução enquanto a unidade de execução
ainda está processando a instrução anterior.
· Sobreposição de execução de instruções: Esta técnica envolve iniciar a execução de
uma instrução antes de concluir a execução da instrução anterior. Por exemplo, enquanto uma
instrução está sendo buscada na memória, a CPU pode começar a executar uma instrução
previamente buscada, aproveitando o tempo ocioso.
4.4.2 Redução do comprimento do caminho de execução (pág. 224)
Discutimos maneiras de reduzir o tempo necessário para executar instruções em uma arquitetura de
computador, como a Mic-1. São apresentadas três técnicas:
· Incorporação do laço do interpretador: Sobrepor o início da próxima instrução ao final
da sequência de microinstruções, reduzindo o tempo total de execução.
· Arquitetura de três barramentos: Utilizar dois barramentos completos de entrada para
a Unidade Lógica e Aritmética (ULA), permitindo adições mais rápidas entre registradores.
· Unidade de busca de instrução (IFU): Introduzir uma unidade independente para
buscar e processar instruções, reduzindo a carga na ULA e antecipando bytes para execução
imediata.
Essas técnicas visam melhorar o desempenho da arquitetura, reduzindo o número de ciclos de clock
necessários para executar instruções e aumentando a velocidade de execução.
4.4.3 Projeto com busca antecipada: a Mic-2 (pág. 229)
A Mic-2 é uma versão aprimorada da Mic-1 que incorpora a Unidade de Busca de Instrução (IFU).
Esta IFU traz melhorias significativas no desempenho da execução das instruções. Em primeiro
lugar, elimina completamente o laço principal, direcionando diretamente para a próxima instrução
ao final de cada uma. Em segundo lugar, evita a sobrecarga da Unidade Lógica e Aritmética (ULA)
ao incrementar o contador de programa (PC). E, em terceiro lugar, reduz o comprimento do
caminho de execução ao montar valores de 16 bits e passá-los diretamente para a ULA como
valores de 32 bits, evitando assim a necessidade de montagem em H. Essas melhorias variam de
instrução para instrução, mas algumas, como LDC _W, são reduzidas de nove para três
microinstruções, representando uma melhoria significativa no desempenho geral das instruções
mais comuns, como ILOAD, IADD e IFJCMPEQ. Essas mudanças resultam em ganhos
substanciais de velocidade de execução, proporcionando uma notável melhoria no desempenho da
Mic-2 em relação à Mic-1.
· ILOAD: Carrega um valor de uma determinada posição de memória na pilha de
operandos.
· IADD: Adiciona dois valores do topo da pilha e armazena o resultado de volta na pilha.
· IFJCMPEQ: Realiza um desvio condicional baseado na igualdade de dois valores do
topo da pilha.
· LDC_W: Carrega uma constante de 2 bytes da memória e a coloca na pilha de
operandos.
4.4.4 Projeto com pipeline: a Mic-3 (pág. 233)
O texto descreve a atualização da Mic-2 para a Mic-3, usando um pipeline para dividir o
caminho de dados em partes independentes. Isso aumenta a velocidade e eficiência da máquina,
permitindo que várias partes do hardware executem instruções simultaneamente, como numa
linha de montagem de uma fábrica de automóveis.
Vamos usar a instrução SWAP como exemplo para comparar como ela é executada na Mic-2 e
na Mic-3:
Na Mic-2:
1. Ciclo 1: Carrega SP para B.
2. Ciclo 2: Subtrai 1 de B.
3. Ciclo 3: Armazena o resultado em MAR e inicia a operação de leitura.
4. Ciclo 4: Lê o valor da memória (MDR).
5. Ciclo 5: Realiza as operações finais e armazena o resultado de volta nos
registradores.
Total de 5 ciclos na Mic-2 para executar a instrução SWAP.
Na Mic-3 com Pipeline:
1. Ciclo 1: Inicia o processo de SWAP, carregando SP para B.
2. Ciclo 2: Continua o processo, realizando a subtração e preparando para armazenar
o resultado.
3. Ciclo 3: Completa a operação e inicia a leitura do valor na memória.
4. Ciclo 4: Lê o valor da memória (MDR) e executa as operações finais.
5. Ciclo 5: Conclui a instrução e prepara para a próxima.
Do total de 5 ciclos na Mic-3 para executar a instrução SWAP, mas como múltiplas
instruções podem estar sendo processadas simultaneamente em diferentes estágios do pipeline, a
Mic-3 tem uma taxa de execução mais eficiente em comparação com a Mic-2.
4.4.5 Pipeline de sete estágios: a Mic-4 (pág. 238)
a Mic-4, introduz-se um pipeline de sete estágios para melhorar a eficiência do processador. A IFU
busca antecipadamente instruções da memória, enquanto a unidade de decodificação as transforma
em instruções IJVM e, em seguida, em micro-operações, que são enfileiradas para execução. Cada
estágio do pipeline executa uma parte específica do processamento da instrução, como busca,
decodificação, execução da ULA e escrita de volta nos registradores.
A unidade de enfileiramento lida com as micro-operações, copiando-as em uma fila e enviando-as
para os registradores de controle apropriados em cada ciclo de clock. O pipeline é projetado para
lidar com desvios condicionais, interrompendo a execução quando necessário e retomando-a
quando o desvio é resolvido. Esse pipeline de alto desempenho permite que as etapas individuais
sejam curtas, aumentando assim a frequência de clock e melhorando o desempenho geral da CPU.
O conceito da Mic-4 é semelhante ao de algumas CPUs modernas, como o Core i7.
4.5 Melhoria de desempenho (pág. 241)
Existem técnicas para melhorar o desempenho do computador, as principais são a melhoria da
arquitetura e as de implementação.
Algumas melhorias somente podem ser realizadas através da alteração da arquitetura, que podem
incluir adicionar novas instruções ou registradores e para ter um melhor aproveitamento delas pode
haver a necessidade de alteração do software para que o mesmo comporte as novas características.
Já na melhoria de implementação, é uma maneira de construir uma nova CPU ou memória para
tornar o sistema mais rápido sem alterar a arquitetura subjacente, isso permitirá novas funções, uso
de novos programas sem perder o que era feito antes.
Com o passar dos anos, projetistas percebem que para ter algo novo, é necessário realmente
começar do zero, a revolução RISC na década de 1980 foi um exemplo disso.
Podemos também citar como formas de melhorar o desempenho o uso de memória cache, previsão
de desvio, execução fora de ordem com renomeação de registrador e execução especulativa.
4.5.1 Memória cache (pág. 241)
Atualmente, com o desenvolvimento de CPUs cada vez mais rápidas em relação às memórias, é um
grande desafio projetar um sistema de memória realmente eficiente e ágil, pois elas não
acompanharam o desenvolvimentos das CPUs, isso é algo que acabou limitando o desenvolvimento
de aparelhos de alto desempenho.
Os processadores modernos exigem um sistema de memória com baixa latência (atraso na entrega
de operandos) e alta largura de banda (quantidade de dados fornecida por unidade de tempo), mas
infelizmente quanto maior a quantidade de dados disponíveis, mais lento ficam.
Para ajudar na solução, usa-se as caches que servem para armazenar palavras de memória
recentemente usadas em uma memória rápida, acelerando o acesso. Ter caches separados para
instruções e dados (cache dividida) é benéfico, pois permite operações independentes em cada
cache, dobrando a largura de banda do sistema de memória.
Além das caches de nível 1 (no próprio chip da CPU), sistemas mais avançados têm caches de nível
2 e até níveis superiores. A cache de nível 2 fica fora do chip da CPU, mas próxima a ele, conectada
por um caminho de alta [Link] caches são geralmente inclusivas, com o conteúdo da cache
de nível 1 também presente na de nível 2, e assim por diante.
Para cumprir seu objetivo as caches dependem de dois tipos de localidade, sendo a especial que
observa que localizações de memória numericamente similares a uma localização recentemente
acessada provavelmente serão acessadas novamente no futuro, sempre que se requisita dados, ela
nos traz mais que os requisitados na intenção de prever o futuro. E a localidade temporal que ocorre
quando localizações de memória recentemente acessadas são acessadas novamente.
Os modelos de cache seguem um padrão, onde a memória principal é dividida em blocos chamados
linhas de cache e cada linha contém 4 a 64 bytes consecutivos. As linhas são numeradas
sequencialmente, começando em 0, quando a memória é referenciada, o circuito de controle da
cache verifica se a palavra está na cache, se sim, o valor é usado, evitando acesso à memória
principal, caso contrário, uma linha é removida da cache e a linha necessária é buscada na memória
ou em uma cache de nível inferior.
A mais simples das caches é a de mapeamento direto, um forte exemplo dela é a cache de um só
nível que contém 2048 entradas, onde cada entrada pode conter exatamente uma linha de cache da
memória principal e se a linha tiver 32 bytes de tamanho, a cache total pode armazenar 64 KB. As
entradas são divididas em três partes, sendo elas:
Bit Valid: indica se há dados válidos nessa entrada (inicialmente todas as entradas são marcadas
como inválidas).
Campo Tag: um valor de 16 bits que identifica a linha de memória correspondente.
Campo Data: contém uma cópia dos dados da memória (32 bytes).
Na cache de mapeamento direto cada palavra de memória é armazenada em um local específico
dentro da cache. Os componentes que envolvem esse armazenamento são:
Campo TAG: corresponde aos bits de tag armazenados em uma entrada de cache e identifica a linha
de memória correspondente da qual os dados vieram.
Campo LINE: indica qual entrada de cache contém os dados correspondentes, se eles estiverem
presentes, em uma cache de mapeamento direto, há apenas uma entrada possível para cada endereço
de memória.
Campo WORD: informa qual palavra dentro de uma linha está sendo referenciada.
Campo BYTE: geralmente não é usado, se apenas um byte for requisitado, esse campo indica qual
byte dentro da palavra é necessário, para uma cache que fornece apenas palavras de 32 bits, esse
campo será sempre 0.
Em uma cache de mapeamento direto, cada palavra de memória é armazenada em um local
específico dentro da cache. O processo envolve os seguintes passos:
Quando a CPU produz um endereço de memória, o hardware extrai os 11 bits do campo LINE do
endereço para indexar a cache e encontrar uma das 2.048 entradas.
Se a entrada for válida, o campo TAG do endereço de memória é comparado com o campo Tag na
entrada da cache.
Se os tags forem compatíveis, a entrada de cache contém a palavra requisitada (presença na cache).
Nesse caso, a palavra pode ser lida diretamente da cache, evitando o acesso à memória principal. Se
a entrada for inválida ou os tags não coincidirem, a palavra necessária não está presente (ausência
na cache). A linha de cache de 32 bytes é buscada na memória e armazenada na cache, substituindo
o que estava lá. Se a linha de cache tiver sido modificada desde que foi carregada, ela deve ser
escrita de volta na memória principal antes de ser sobrescrita. Apesar da complexidade da decisão,
o acesso à palavra necessária é extremamente rápido. O processador pode receber a palavra da
cache simultaneamente ou até antes de determinar se é a palavra correta (por meio da comparação
de tags).
Esse esquema de mapeamento coloca linhas de memória consecutivas em linhas de cache
consecutivas. Até 64 KB de dados contíguos podem ser armazenados na cache. No entanto, quando
a diferença entre os endereços de duas linhas é exatamente 64 KB (65.536 bytes) ou um múltiplo
inteiro desse número, elas não podem ser armazenadas na cache ao mesmo tempo (devido ao
mesmo valor de LINE). Isso pode resultar em mau desempenho se ocorrer com frequência.
Caches de mapeamento direto são comuns e eficazes, pois colisões como essa ocorrem raramente
ou nunca. Ter caches separadas para instruções e dados também ajuda a lidar com padrões de
memória conflitante.
As caches associativas de conjunto são uma organização intermediária entre as caches totalmente
associativas e as de mapeamento direto. Nesse tipo de cache, os blocos podem ser encontrados em
um único conjunto, mas cada conjunto possui várias entradas (ou vias).
Previsão de desvio: o pipelining é uma técnica usada em processadores para melhorar a eficiência
e o desempenho do chip. Ele divide o processamento de uma instrução em múltiplos estágios,
permitindo que várias instruções sejam executadas ao mesmo [Link] e eficiência: o
pipelining permite que várias instruções sejam processadas simultaneamente, melhorando a
eficiência do sistema. Desvios e problemas: o modelo pipelined funciona bem com código linear,
mas programas reais contêm instruções de desvio (como condicionais e incondicionais). Desvios
causam problemas no pipeline, pois a busca precisa decidir de onde buscar antes de saber o tipo de
instrução. Máquinas com pipeline têm uma posição de retardo após desvios incondicionais, onde a
próxima instrução é executada mesmo que logicamente não devesse ser. Desvios condicionais são
ainda mais problemáticos, pois a busca não sabe de onde ler até mais adiante no pipeline. Máquinas
com pipeline protelam até saberem se um desvio condicional será tomado ou não. Protelação
excessiva (3-4 ciclos por desvio) prejudica o desempenho. A maioria das máquinas prevê se um
desvio será tomado. Uma abordagem simples é considerar que desvios para trás serão tomados e
desvios para frente não serão. Desvios para trás geralmente ocorrem no final de laços, tornando essa
previsão razoável.
Previsão dinâmica de desvios: previsões exatas permitem que a CPU funcione em alta velocidade.
Pesquisas focam em melhorar algoritmos de previsão de desvio. Uma abordagem é manter uma
tabela histórica de desvios condicionais para consultas futuras. A tabela de histórico é usada para
prever desvios em processadores. Pode ser organizada de maneira semelhante a uma cache. Um
exemplo: máquina com instruções de 32 bits, alinhadas por palavra (2 bits de ordem baixa = 00).
Tabela de histórico de mapeamento direto com 2^n entradas. Os n + 2 bits de ordem baixa de uma
instrução de desvio são usados como índice na tabela. Compatibilidade verifica se o endereço
armazenado corresponde ao endereço do desvio. O bit de previsão é usado para prever o desvio.
Quando se atinge a saída de um laço, previsões erradas podem ocorrer. Para eliminar isso, dar uma
segunda chance à entrada da tabela. Dois bits de previsão: um para o que o desvio “deve” fazer e
outro para o que fez da última vez. Pode ser visto como uma máquina de estado finito com quatro
estados. Todos os nossos microprogramas podem ser considerados Finite State Machines (FSMs).
Cada linha representa um estado específico com transições bem definidas para outros estados.
FSMs são amplamente usadas no projeto de hardware. Em desvios condicionais, o alvo geralmente
é conhecido (endereço explícito ou deslocamento relativo). Algumas instruções de desvio calculam
o endereço de destino com base em aritmética de registradores.A previsão de desvio é inútil se o
endereço de destino for [Link] abordagem é armazenar o endereço de destino da última
vez na tabela de histórico. Outra abordagem é monitorar os últimos k desvios condicionais,
independentemente das instruções. O número de k bits é comparado com entradas de uma tabela de
histórico com chave de k bits. Essa técnica surpreendentemente funciona bem.
Previsão estática de desvio: As técnicas de previsão de desvio discutidas até agora são dinâmicas e
adaptam-se ao comportamento do programa em tempo de execução. No entanto, essas técnicas
requerem hardware especializado e complexidade no chip. Uma abordagem diferente é envolver o
compilador, o compilador pode identificar padrões, como loops, e prever que desvios serão
tomados. Algumas máquinas, como a UltraSPARC III, possuem um segundo conjunto de instruções
de desvio condicional para uso pelo compilador, essas instruções permitem que o compilador
especifique se acha que o desvio será tomado ou não. A última técnica é baseada na determinação
de perfil, o programa é executado em um simulador, capturando o comportamento dos desvios, essa
informação é usada pelo compilador para gerar instruções de desvio condicional específicas para o
hardware.
Execução fora de ordem e renomeação de registrador: Pipelining é uma técnica para melhorar o
desempenho da CPU, permitindo a execução simultânea de várias instruções. Em uma máquina
pipelined, as instruções são divididas em estágios e processadas em paralelo, a execução em ordem
(sem reordenação) nem sempre é ideal devido a dependências entre instruções (como RAW - Read
After Write), algumas CPUs permitem saltar instruções dependentes para alcançar instruções
futuras não dependentes.
Exemplo de Sequência de Execução:
○ O exemplo mostra uma máquina com 8 registradores visíveis e 4 estágios de
execução.
○ As instruções aritméticas usam 3 registradores (2 operandos e 1 resultado).
○ A escrita retroativa no registrador de destino ocorre após 2 ciclos para instruções
simples e 3 ciclos para instruções complexas.
○ A unidade de decodificação pode emitir até 2 instruções por ciclo de clock.
Tabela de Pontuação (Scoreboard):
○ A unidade de decodificação monitora o estado dos registradores para decidir se pode
emitir uma instrução imediatamente.
○ A tabela de pontuação rastreia a utilização dos registradores e ajuda a evitar
conflitos.
A tabela de pontuação monitora o uso dos registradores durante a execução.
i. Cada registrador tem um contador que informa quantas vezes é usado como
fonte por instruções em execução.
ii. Contadores para registradores de destino também são mantidos.
iii. A tabela de pontuação também pode monitorar a disponibilidade de unidades
funcionais.
Regras para Emissão de Instruções:
iv. Dependências RAW (Read After Write) ocorrem quando uma instrução
precisa usar um resultado que ainda não foi produzido.
v. Dependências WAR (Write After Read) e WAW (Write After Write) são
conflitos de recursos.
vi. Se não houver dependências e a unidade funcional estiver disponível, a
instrução é emitida.
Exemplo de Execução:
vii. O exemplo mostra a execução de instruções em uma máquina superescalar.
viii.A protelação ocorre quando há dependências entre instruções.
ix. A próxima instrução (15) não tem conflitos e poderia ser emitida, mas o
projeto exige emissão em ordem.
Execução especulativa: a reordenação de instruções pode melhorar o desempenho. Programas
podem ser divididos em blocos básicos, que são sequências lineares de código sem estruturas de
controle. Um gráfico orientado conecta os blocos básicos.
A execução especulativa permite executar código antes de saber se será necessário. A elevação
envolve mover instruções para cima no gráfico para iniciar operações mais [Link]
instruções entre blocos básicos requer suporte do compilador e hardware.
A execução especulativa não tem resultados irrevogáveis. Renomear registradores de destino evita a
sobrescrita prematura. A tabela de pontuação monitora essas operações. O código especulativo pode
causar exceções, como falhas de cache ou divisões por zero. Falsas faltas de página podem afetar o
desempenho, uma solução é usar instruções SPECULATIVE-LOAD que desistem se a palavra não
estiver na cache.
A divisão por zero pode ocorrer mesmo com código explícito para evitá-la, introduzir instruções
especiais e um bit envenenado nos registradores pode ajudar a lidar com exceções.