Arquiteturas de computadores! slidesversaopdf
-
Upload
marcelle-guine -
Category
Documents
-
view
1.690 -
download
1
description
Transcript of Arquiteturas de computadores! slidesversaopdf
1
Arquiteturas de
ComputadoresUma abordagem informal
2
O que é um computador?
Computadores são ferramentas Servem para resolver problemas
3
Qualquer sistema computacional possui 3 características em geral
Processamento Memória (armazenamento) Comunicação
Um celular, por exemplo, é um sistema computacional que possui como principal característica a comunicação
4
Curiosidades
No Brasil, segundo o censo de 2010, há 190 milhões de habitantes Segundo o Portal Teleco, site de informações sobre telecomunicações, dados de julho de 2011 mostram que há 220 milhões de celulares no país Pois é, há mais celulares do que pessoas no Brasil. O mercado para profissionais de computação é imenso!
5
Uma ferramenta deve possuir objetividade (realizar sem restrições a tarefa para a qual ela foi criada) Além disso, há 3 aspectos importantes que definem sua qualidade:
Desempenho (tempo de processamento) Custo-benefício Facilidade de uso
6
Você pode optar por uma memória de 2 GB a R$ 500,00 ou por uma de 4 GB a R$ 600,00. Qual é a melhor opção?
2 GB 4 GB
7
A resposta é: depende Se a sua aplicação não necessitar de mais de 2 GB de memória, você estará gastando R$ 100,00 a mais que o necessário
Isto faz parte da questão do custo-benefício No caso de carros e TVs, produtos novos e melhores são lançados a preços mais caros que os anteriores Já produtos da indústria computacional tem preços iguais ou menores que os anteriores, e ainda assim são geralmente melhores (mais ágeis)
8
Ok, mas chega de filosofar...
Vamos à matéria de Arquiteturas de Computadores!
9
Como funciona um computador? Algoritmos implementados por um programa
escrito em uma sequências linguagem de de instruções programação
Programadores se comunicam com computadores através de uma linguagem de programação
10
Para haver comunicação, é necessário que haja uma linguagem em comum...
...ou um tradutor
11
Linguagens em geral...
Mais Mais complexa chinês português, simples inglês etc (computador (usuário entende) entende)
É necessária uma espécie de tradução das linguagens comuns para a linguagem binária, que é o que o computador “entende” de fato
Veremos a seguir que, na verdade, tradução é apenas parte do processo
12
Ling. de Prog. Alto Nível
Montagem
Sistema Operacional
Macroprogramação
A nível de software, temos: N5
N4
N3
N2
Montador
Compilação
TRADUÇÃO
Lê o código e, usando as informações nele contidas, manipula a memória.
Interpretação Parcial
Interpretação
13
A nível de hardware, temos:
Microprogramação
Circuitos Digitais
N1
N0
Interpretação
Só agora são executadas as instruções
Aqui, há um número muito maior de instruções do que havia no início. Com isso, mais circuitos devem ser criados, aumentando o custo. Portanto, linguagem simples é necessária.
14
Tradução X Interpretação
O esquema das etapas de execução de um programa apresentado anteriormente não é uma generalização
Isto porque existem linguagens compiladas (utilizam tradução) e outras, interpretadas
A tradução é feita uma vez apenas. Já a interpretação é realizada cada vez que o programa é executado
15
Tradução:
Interpretação:
______________________________________________________
________________________________________________________________________________________
L2
L1
Execução
✔
✔
✔
✔
✔
✔
✔
.
.
.
______________________________________________________
L2
✔
✔
✔
✔
.
.
.L1
Tempo de execução
16
Mas afinal, para que tantas etapas na execução de um programa?
O esquema abaixo mostra um dos problemas que ocorreriam caso as instruções das linguagens em alto nível fossem executadas sem todas essas etapas
C Pascal Cobol C++ .....m
Pentium 4 I7 Xeon AMD .....n
17
Note que, para cada linguagem diferente, seria necessária uma maneira diferente de fazer suas instruções serem executadas. Isso para cada processador
Para cada nova linguagem ou novo processador, já haveria uma nova necessidade para que o programa fosse executado
No total, teríamos m*nnecessidades diferentes!
18
Todas aquelas etapas servem então para que, utilizando o conceito de divisão e conquista, os processadores não precisem “se preocupar” com linguagens, e vice-versa
As instruções complexas são reduzidas a instruções em uma linguagem simples, entendida por todos os processadores
C Pascal Cobol C++ .....m
Pentium 4 I7 Xeon AMD .....n
19
Agora, são apenas m+n maneiras diferentes de execução de programas Intuitivamente, temos um custo maior por ter
tantas etapas... mas não é bem assim. Dos níveis N5 até N3, o custo muitas vezes nem chega ao usuário, pois o software é compilado antes de chegar até ele
É importante ressaltar que uma instrução em L2 não pode nunca ser diretamente executada no nível N0
Depois de N2, não ocorre mais tradução, apenas interpretação
20
Na interpretação, as instruções são apenas convertidas nas ordens em que serão executadas, o que acontece de N1 para N0 no esquema apresentado anteriormente
Relembrando:
A seguir, uma melhor representação das etapas de um programa até que ele esteja em L2
Macroprogramação
Microprogramação
Circuitos digitais
L2
L1
Instruções
Interpretação
Execução
21
____________________________________________________
_____________________
_____________________
_____________________
+ + + .............. +
010010101101001010101010101011100110
...
101101010101010100101101
...
L5
L4
L3 -> L2 + L1
L2
O programa em L3 tem também os endereços de memória envolvidos no processo
Compilação
Montagem
22
Curiosidades
Em Delphi, é possível manipular o programa no nível de montagem. Com isso, pode-se ter uma noção maior de quanto tempo levará sua execução Atualmente, no caso de dispositivos móveis, por exemplo, programa-se em alto nível e depois acessa-se o programa em nível de montagem, para que sejam reprogramadas apenas as partes “ruins”
23
Com o conceito de linguagens (L1, L2, L3 etc) apresentado, temos também o conceito de máquinas virtuais. Por exemplo:
Circuitos digitais
Microprogramação
Macroprogramação
L2
L1
Instruções
Máquina física
Máquina virtual L2
Máquina virtual L3
24
Generalizando para o topo, temos:
Linguagem de programação de alto nível
L5Compilação
.
.
.
.
.
.
.
.
.
.
Máquina virtual à vista do usuário
25
Abstração Considere o seguinte esquema:
C Pascal Java
Windows Linux
Processador 1
Processador 2
Compilador A
Compilador B
Compilador D
Compilador C
Política de compatibilidade da Intel: todas as instruções entendidas por um Pentium 4 são entendidas também pelo I7 (mais avançado)
Vimos anteriormente que os compiladores A, B, C e D não são necessariamente diferentes, assim como os processadores
Porém, com processadores que não sejam da Intel, é possível que sejam necessários diferentes compiladores para cada situação
A linguagem Java, teoricamente, não possui restrições de plataforma
Veremos a seguir que ela utilizauma espécie de disfarce...
26
27
Através da abstração, a JVM (Java Virtual Machine) “esconde” as complexidades que envolvem as plataformas
Java
____________________________________________________________________
JVM
Java ByteCode
Interpretação
__________________________________________
Máquina Virtual Java
28
Hardware X Software Considere o esquema abaixo:
N5
N4
N3
N2
N1
N0
COM
PLEX
IDAD
E
hardware
software
29
Mas, complexidade de...? Funcionalidade Cabe ao arquiteto do sistema saber onde cada
função deve ser implementada Adição e multiplicação são sempre feitas em
hardware, pois o desempenho é muito melhor. Mas então, por que não implementar tudo em hardware?
Custo é a resposta. Operações mais sofisticadas, se implementadas todas em hardware, aumentariam demais o custo (circuitos demais)
30
Não são poucos os circuitos necessários para implementar operações de soma e multiplicação em hardware. Agora imagine implementar um programa complexo inteiro usando apenas esses circuitos digitais!
Por outro lado, imagine também o gasto de tempo desnecessário que haveria se,
para cada vez que precisássemos somar ou multiplicar, fosse neces- sário traduzir as instruções em software para hardware!
31
Outra desvantagem de hardware é: se ocorrer algum problema físico com as peças, como reparar?
Softwares são mais baratos e, caso haja algum erro, é muito mais fácil localizar e consertá-lo. Porém, como já foi citado, o desempenho é menor
32
Computadores:Visão geral
CACHE
Processador / CPU
CACHE
Memória RAM
endereços
dados
leitura / escrita
Hard Disk (HD)
Disquete / Blu-ray
Rede Wi-Fi
GPU
USB
Central Processing Unit
Graphics Processing Unit
.Fonte
Placa mãe
33
Vamos começar falando superficialmente sobre o processador
Sua função é executar programas armazenados na memória principal, buscando instruções, identificando e executando as mesmas uma após a outra
Unidade de controle (UC): É responsável pela busca das instruções na memória principal e pelas suas identificações
Unidade Lógica Aritmética (ULA): É responsável pela realização de operações como adição, AND booleano, entre outras, necessárias para a execução das instruções
Registradores: Juntos, formam uma pequena memória de alta velocidade, que armazena resultados temporários e certas informações de controle. Cada registrador possui uma determinada função. Os dois mais importantes são o contador de programa, PC (responsável por apontar a próxima instrução a ser executada), e o registrador de instruções, IR (responsável por armazenar a instrução que será executada)
34
O processador executa as instruções através de uma pequena sequência de passos conhecida como o ciclo busca-decodifica-executa
Este ciclo é o centro da operação de todos os computadores
Veremos mais tarde por que o ciclo é tão determinante na execução das instruções de um programa
35
A memória é a parte do computador onde programas e dados são armazenados
Sem ela, os processadores não poderiam escrever informações, então não existiria nenhuma maneira de um computador armazenar um programa e dificilmente poderia executar algum
Em um computador, geralmente existe memória primária e memória secundária
36
Memórias primárias: memórias que o processador pode endereçar
diretamente elas geralmente fornecem uma ponte para as
memórias secundárias, mas sua função principal é conter a informação necessária para o processador num determinado momento – por exemplo, dados dos programas em execução
Exemplo: a memória principal, sobre a qual falaremos mais detalhadamente ao analisar o Modelo de Von Neumann
37
Memórias secundárias: não podem ser endereçadas diretamente; a
informação precisa ser carregada em memória primária antes de poder ser tratada pelo processador
não são fundamentais para a parte operacional do computador. Computadores feitos exclusivamente para efetuar cálculos matemáticos complexos, por exemplo, não precisam tanto desse tipo de memória
são geralmente não-voláteis, permitindo guardar os dados permanentemente
Exemplos: HD, CDs e DVDs
38
RAM x CACHE Memória RAM: É a memória principal da máquina,
onde todos os processos necessários para a inicialização e execução de programas armazenados em uma memóriasecundária são carregados
Memória Cache: É umamemória com maior veloci-dade de acesso para o pro-cessador que a RAM. É localizada embutida no processador justamente para aumentar a velocidade de acesso. Entretanto, seus dados são temporários
39
Pode-se dizer que o “mundo” do computador se resume a (esquema do Modelo de Von Neumann):
Memória Processador
.
.
.
.
.
0 ou 1
Sequência de bits
BuscaArmazena Processa
BUSCA próxima instrução L2IDENTIFICA (decodifica e entende)EXECUTA cada instrução L1
40
Curiosidades
1 byte equivale a 8 bits 1 kilobyte (KB) equivale a 1.000 bytes, 1 megabyte (MB) equivale a 1.000.000 bytes, 1 gigabyte (GB) equivale a 1.000.000.000 bytes, e assim por diante... ...ou não?
41
Curiosidades
A memória é contada em potência de 2. Quando dizemos “1 GB de memória RAM”, estamos usando a sigla incorreta para gibibytes (GiB) Portanto, 1 GB coloquialmente representa 1 GiB, que equivale a 2³⁰ bytes (1.073.741.824 bytes); quase 7,4% mais do que 1.000.000.000 bytes Mas você provavelmentejá sabia disso
42
A seguir, será descrita mais detalhadamente a arquitetura interna de um processador
Mas antes, vamos resolver uma espécie de enigma, apresentado em uma aula de Arquiteturas de Computadores, pelo professor Vinod
endereços
dados
controle (r/w)
memória
. . .
. . .
. . .
processador
43
Dado o esquema do slide anterior, deseja-se que os dados lidos da memória passem por todos os nove pontos do processador, utilizando o mínimo possível de fios
Os fios são todos retos, ou seja, a solução para o problema será o mínimo de linhas retas que passem por todos os pontos
. . .
. . .
. . .
44
Dado o esquema do slide anterior, deseja-se que os dados lidos da memória passem por todos os nove pontos do processador, utilizando o mínimo possível de fios
Os fios são todos retos, ou seja, a solução para o problema será o mínimo de linhas retas que passem por todos os pontos
. . .
. . .
. . .
Uma configuração óbvia seria:
45
Com essa solução, temos 5 retas Será que 5 é realmente o número mínimo de
retas? Pense mais um pouco e clique para ver a
resposta...
. . .
. . .
. . .
46
Com essa solução, temos 5 retas Será que 5 é realmente o número mínimo de
retas? Pense mais um pouco e clique para ver a
resposta...
. . .
. . .
. . .
Começando de uma forma diferente
47
Com essa solução, temos 5 retas Será que 5 é realmente o número mínimo de
retas? Pense mais um pouco e clique para ver a
resposta...
. . .
. . .
. . .
Começando de uma forma diferente
Seguindo por mais 2 pontos... Podemos usar apenas mais 2 retas!
48
Com essa solução, temos 5 retas Será que 5 é realmente o número mínimo de
retas? Pense mais um pouco e clique para ver a
resposta...
. . .
. . .
. . .
Começando de uma forma diferente
Seguindo por mais 2 pontos... Podemos usar apenas mais 2 retas! Por que não fazer isso...
49
Com essa solução, temos 5 retas Será que 5 é realmente o número mínimo de
retas? Pense mais um pouco e clique para ver a
resposta...
. . .
. . .
. . .
Começando de uma forma diferente
Seguindo por mais 2 pontos... Podemos usar apenas mais 2 retas! Por que não fazer isso... ...para depois fazer isso?
50
Com essa solução, temos 5 retas Será que 5 é realmente o número mínimo de
retas? Pense mais um pouco e clique para ver a
resposta...
. . .
. . .
. . .
Começando de uma forma diferente
Seguindo por mais 2 pontos... Podemos usar apenas mais 2 retas! Por que não fazer isso... ...para depois fazer isso? E finalmente isso. 4 retas!
51
Curiosidades
Por que esta última solução não é tão óbvia quanto a primeira, se não havia nenhuma restrição de que as retas não podiam ultrapassar a caixa? Não estamos acostumados a pensar além do que temos de concreto. Em países como os EUA, por exemplo, os alunos são estimulados desde o primário a “pensar fora da caixa” Não é à toa que a maioria das descobertas e invenções vem de países desenvolvidos Então, lembre-se: “Think outside the box”
52
Processadores e Microprogramação
Vamos agora analisar mais a fundo o Modelo de Von Neumann
Vamos definir conceitos como registradores, barramentos, Unidade Lógica Aritmética (ULA), entre outros
Em seguida, construiremos nosso processador utilizando todas as componentes apresentadas
53
Registradores Locais onde são armazenadas informações A diferença dos registradores para as células da
memória principal é que os primeiros estão localizados dentro do processador. Isso faz com que informações contidas nos registradores sejam buscadas bem mais rapidamente para processamento
Representação*:
Registrador
*ATENÇÃO: tanto esta como as outras representações feitas aqui não fazem parte de nenhum tipo de convenção – são meras ilustrações!
54
Barramentos Conjuntos de fios por onde passam bits de dados
ou de controle Em outras palavras, artifícios utilizados para
transmitir sinais de um dispositivo para outro Representação:
ou
n
Onde n é o número de fios do barramento, lembrando que por cada fio passa um bit por vez
55
Multiplexadores (MUX) Circuitos que recebem entradas, selecionam uma
delas através de sinais de controle e as liberam como saída
Representação:
MUX
56
Unidade Lógica Aritmética (ULA) Dispositivo que recebe dois dados de entrada A e
B, opera-os sobre uma função pré-determinada e libera um dado de saída
Recebe bits de controle que especificam a operação a ser realizada
A ULA poderá fazer 4 operações em nossa abordagem, portanto são necessários 2 bits de controle (00, 01, 10 e 11 = 4 possibilidades)
Por ora, a única operação relevante é a soma (00) Representação:
ULA
A B
57
Deslocador Dispositivo que recebe um número binário e
multiplica ou divide por 2, se desejado for, através do deslocamento à direita ou à esquerda
Deslocar à direita significa “apagar” o bit mais à direita e adicionar um 0 à esquerda, e o procedimento é exatamente o oposto no deslocamento à esquerda
Exemplo: 0010 (=2) Deslocando à direita (divisão): 0001 (=1) Deslocando à esquerda (multiplicação): 0100 (=4)
Representação:
DES
58
Vamos falar mais detalhadamente sobre registradores
Sabemos que registradores contém dados que tanto podem ser lidos quanto sobrescritos
Por isso, cada registrador precisa receber um bit que controla sua entrada, que chamaremos de HE, e outro que controla sua saída, HS
Registradores possuem na sua estrutura interna um circuito flip-flop para cada bit que armazena, cada um deles conectado a um fio tanto do barramento de entrada como do barramento de saída
59
Abaixo, esquematizada a estrutura interna de um registrador de 8 bits:
HE (habilita entrada)
ENTRADA
HS (habilita saída)
SAÍDA
Tri-state buffer
60
Tri-state buffers são circuitos que evitam conflitos entre as saídas para o barramento, fazendo um tipo de “desconexão virtual” entre registradores e barramentos
Esses conflitos seriam possíveis porque várias saídas de registradores estarão conectadas a um mesmo barramento
Nossa arquitetura terá 16 registradores para armazenar dados, dentre os quais alguns tem funções especiais: PC (Program Counter), IR (Instruction Register) e AC (Accumulator)
61
O Program Counter contém o endereço da próxima instrução a ser buscada na memória principal
O Instruction Register armazena a instrução buscada na memória. Ou seja, IR = MP[pc], sendo IR o conteúdo do registrador e pc um endereço da memória principal MP
Accumulator é um registrador que armazena valores intermediários, que não seriam úteis ao final da operação
Haverá em nossa arquitetura, além dos 16, outros registradores espalhados no processador: dois latches, A e B, o Memory Adress Register (MAR) e o Memory Buffer Register (MBR)
Os latches servem para “segurar” dados no barramento e evitar que sejam sobrescritos
Precisamos sempre lembrar que os fios do barramento apenas conduzem eletricidade, que já é instável por si mesma
Latches serão importantes então para captar dados do barramento que estão prontos para serem processados, isto é, estão estáveis
É como tirar uma foto: énecessário esperar até quetodos estejam parados
62
63
O MAR é ligado ao barramento de endereços, que controla exclusivamente o fluxo de endereços que o processador envia para serem consultados na memória
O MBR é ligado ao barramento de dados do sistema, e guarda os dados buscados na memória que serão processados na CPU ou os dados resultantes de algum processamento e que serão escritos na memória, ou até mesmo processados novamente
64
Podemos agora começar a “montar” o nosso processador
Já foi dito que, para começar, vamos pensar apenas em somar dados. Para isso, precisamos simplesmente ler dois deles e somá-los
Dois registradores e uma ULA são necessários
PCIRACR1R2....
ULA
Escolheremos então, dentre nossos 16 registradores, um registrador R1 e outro R2 para ler e somar seus dados
65
Intuitivamente, ligamos tudo através de barramentos
Vamos guardar o resultado da soma no registrador R3
ULA
PCIRACR1R2
.
.
.
R3
Barramento A
Barramento B
Barramento C
66
Ainda não é o caso, mas se quiséssemos multiplicar ou dividir um número por 2, precisaríamos adicionar um deslocador
Posicionando os 2 dispositivos separadamente, teríamos a vantagem de fazer operações simultâneas. Colocando ambos juntos, podemos usá-los consecutivamente, isto é, multiplicar ou dividir direto o resultado de uma soma, sem precisar armazená-lo antes
67
Mesmo que ainda não seja necessário, vamos adiantar um deslocador para a nossa máquina, colocando o mesmo logo após a ULA:
ULA
PCIRACR1R2
.
.
.
R3
DES
Barramento A
Barramento B
Barramento C
68
Agora, precisamos pensar fisicamente Imagine se nossa soma fosse R1 ← R1 + R2, isto
é, o registrador R1 receberia o resultado da soma de seu valor atual com o valor de R2
Enquanto a ULA processa os primeiros bits de cada dado, o barramento de saída está sendo modificado e, portanto, os novos valores já estão sendo salvos em R1, o que poderia causar um erro na soma
Uma das soluções para esse impasse é adicionar latches antes da ULA, que guardarão os valores originais de R1 e R2 enquanto são processados
69
ULA
PCIRACR1R2
.
.
.
R3
DES
LA LB
Barramento A
Barramento B
Barramento C
70
Ok, mas temos que pensar também na interação processador-memória
É preciso adicionar, então, MAR e MBR. O MAR controla apenas informações (endereços) que vão do processador para a memória, mas o MBR controla o fluxo de dados de toda a interação, incluindo os dados de saída do deslocador, dados de entrada para a ULA e dados que serão lidos ou escritos na memória
MAR MBR
71
Em nossa arquitetura, o MAR poderá ser carregado a partir do latch B
É interessante possibilitar o processamento de dados direto do MBR para a ULA, poupando assim o tempo de selecionar o registrador (entre 16 deles!) desejado para armazenar o resultado da operação, e depois selecionar o mesmo novamente para buscar os bits e processá-los
72
Porém, repare que o MBR estará conectado à ULA, junto com o latch A. Há um conflito visível nessa configuração
Lembre-se de que a ULA é apenas um circuito combinatório. Os bits de controle que recebe são apenas para especificar a operação a ser feita, o que significa que está sempre recebendo dados
MBR
ULA
Latch A
73
PCIRACR1R2
.
.
.
R3
DES
LA LB
MAR
MBR
Barramento A
Barramento B
Barramento C
ULA
conflito
74
PCIRACR1R2
.
.
.
R3
DES
LA LB
MAR
MBR
Barramento A
Barramento B
Barramento C
ULA
conflito
Sem pânico!
A solução é simples!
75
ULA
PCIRACR1R2
.
.
.
R3
DES
LA LB
MAR
MBR
AMUX
Barramento A
Barramento B
Barramento C
Basta adicionar um multiplexador, que chamaremos de AMUX (multiplexador A):
76
O multiplexador controlará qual dos dados deve ser enviado para a ULA
Mas e se a instrução a ser executada fosse R1 ← R1 + R2 + R3 + R4 ?
Intuitivamente, a solução seria uma ULA com 4 entradas, com a vantagem de resolver essa soma sem precisar guardar nenhum resultado intermediário
ULA
A B C D
77
Sendo assim, uma ULA com 4 entradas é mais vantajosa para os casos de soma com 4 parcelas. O problema é que isto não será econômico se essa
soma não ocorrer com frequência E pensando bem, de fato ela não é tão comum
quanto uma soma com apenas 2 operandos, para a qual basta uma ULA com 2 entradas
Unindo o útil ao agradável, por que não pensar em uma solução alternativa para realizar esta soma, ainda sem precisar salvar valores intermediários?
78
Eis a solução:
ULA 1 ULA 2
ULA 3
A B C D
E F
ULA 4
A B C D
79
Curiosidades
Fabricar 3 ULA’s com 2 entradas é ainda mais econômico do que projetar uma única ULA com 4 Além disso, outra vantagem em reduzir o tamanho das ULA’s pode ser explicada fazendo uma analogia com a forma como são fabricados os próprios processadores
80
Curiosidades
Processadores são obtidos através de fatias redondas de silício, que podem ser divididas em partes pequenas ou nem tanto:
81
Curiosidades
Podemos pensar em cada um desses pedaços como uma componente do processador As fatias, porém, sempre tem impurezas Imagine, por exemplo, a fatia abaixo, onde as manchas vermelhas são suas impurezas
82
Curiosidades
Cada parte atingida por uma impureza está inutilizada Pensando nas partes como componentes, aquelas que foram atingidas não irão funcionar Na partição em componentes menores, repare que bem mais delas saíram ilesas
83
Curiosidades
Cada parte atingida por uma impureza está inutilizada Pensando nas partes como componentes, aquelas que foram atingidas não irão funcionar Na partição em componentes menores, repare que bem mais delas saíram ilesas
O que restou:
84
Recapitulando, temos então um processador que realiza a soma que queríamos
ULA
PCIRACR1R2
.
.
.
R3
DES
LA LB
MAR
MBR
AMUX
Barramento A
Barramento B
Barramento C
Dentro do Processador
Fora do Processador(memória)
85
É importante ressaltar que as linhas que ilustram os barramentos estão sendo mostradas apenas nos caminhos que queríamos para a soma
Na verdade, todos os registradores estão conectados aos barramentos A, B e C, e uma estrutura de controle é que determina de onde são lidos os dados e onde eles são gravados
Falando em controle, já vimos que não são só os registradores que precisam de bits de controle para que o processador possa funcionar
A seguir, vamos quantificar os bits de controle de todas as componentes e descrever para que eles servem em cada uma delas
86
Temos 16 registradores para leitura e escrita A intenção é ler sempre 2 dados (um vai para o
barramento A e o outro para o barramento B), realizar uma operação na ULA e/ou no deslocador e, quando necessário, armazenar o resultado em outro registrador
Isso faz com que precisemos de 16 bits de controle para cada uma dessas operações. Entenderemos o motivo logo a seguir
87
A posição do registrador-alvo seria dada pela posição do bit 1 em meio aos 16 bits de controle
Por exemplo, na operação de leitura cujo controle é: 0000000000001000 seria lido o dado do quarto registrador
PCIRACR1R2
.
.
.
R3
R13
00 00 000000001000
88
A posição do registrador-alvo seria dada pela posição do bit 1 em meio aos 16 bits de controle
Por exemplo, na operação de leitura cujo controle é: 0000000000001000 seria lido o dado do quarto registrador
PCIRACR1R2
.
.
.
R3
R13
000100
0
89
Mas não é por mágica que a informação do registrador é lida quando ele recebe o bit 1
Na estrutura interna dos registradores, vimos que eles recebem um bit HE para habilitar sua entrada e outro, HS, para habilitar sua saída
No exemplo anterior, a operação a ser feita era de leitura. Então, é a saída do registrador que precisa ser habilitada, para que a informação seja jogada em um dos dois barramentos (A ou B)
Bits de controle especificam qual dos dois barramentos será o destino dos dados, mas isso por ora não tem importância
90
Caso a operação fosse de escrita no registrador, o bit que iria para cada registrador corresponderia ao HE, e aquele que recebesse 1 seria sobrescrito
Mas, e se quisermos simplesmente gravar o resultado no MBR, e não gravar em nenhum registrador?
Para isso, precisamos de um 17º bit de controle (EnC, Enable C),
ao barramento C, que impeçaescrita em qualquer um dos 16 registradores caso isso não sejadesejado
91
Os outros registradores (latches A e B, MAR e MBR) recebem, separadamente, também 2 bits de controle (HE e HS), exceto pelo MBR que recebe 4
Os outros 2 bits do MBR são comandos de leitura e escrita na memória (chamaremos RD, leitura, e WR, escrita)
Nossa ULA recebe 2 bits de controle para as 4 operações que pode executar a partir de dados de entrada A e B: 00 para A + B, 01 para a operação lógica A AND B, 10 para simplesmente retornar A, e 11 para retornar o inverso de A
92
O deslocador também precisa receber 2 bits de controle (00 para não deslocar, 01 para deslocar à esquerda, 10 para deslocar à direita e 11 nada faz)
Finalmente, o multiplexador recebe apenas um bit para controlar qual dos dois dados que ele recebe deve seguir (0 para seguir o valor de latch A e 1 para seguir o valor que veio de MBR)
Na ordem, temos então 16 + 16 + 17 + 2 + 2 +2 + 4 + 2 + 2 + 1 = 64
À primeira vista, precisamos apenas de um registrador especial para controle, de 64 bits, com um bit para cada sinal
Mas serão todos esses 64 bits realmente necessários?
93
A verdade é que muito desses bits podem ser
dispensados
Fazendo uma análise mais cuidadosa dos controles de cada componente, veremos por que muitos desses sinais são desnecessários
Mas, antes disso, vamos introduzir uma nova componente que será necessária para nos livrarmos de alguns bits
94
Decodificador Dispositivo que, para a nossa abordagem,
receberá um número binário de 4 bits e, com ele, liberará 16 bits onde o bit na posição i será 1 e os outros, 0
OBS: i é o valor na base 10 do binário original. Note que i está entre 0 e 15
Exemplo: se o decodificador recebe o binário 0011 (=3), a saída será 0000000000001000
Representação:
DEC
3 2 1 0
95
Afinal, qual a grande utilidade do decodificador? Pense: se com apenas 4 bits, podemos
representar até o número 15, então é possível especificar em 4 bits qual o registrador a ser selecionado
Adicionando decodificadores para A, B e C, passamos a conta de 16 + 16 + 17 bitspara 4 + 4 + 5, já que o bit EnCpermanece
Pois é, já economizamos 36 bitsde controle
96
Os latches A e B são registradores que não precisam de controle na saída. Precisam apenas controlar os dados que entram, que vão sobrescrever as informações que eles guardavam até então
A saída dos latches pode ficar sempre liberada, já que a função deles é simplesmente salvar valores e não deixar que outras informações de um barramento passem por cima deles
HS
HE
97
Os latches A e B são registradores que não precisam de controle na saída. Precisam apenas controlar os dados que entram, que vão sobrescrever as informações que eles guardavam até então
A saída dos latches pode ficar sempre liberada, já que a função deles é simplesmente salvar valores e não deixar que outras informações de um barramento passem por cima deles
HS
HE
98
Para a nossa abordagem, o MAR também precisa de controle apenas na entrada, para controlar os endereços que nele entram e que serão então enviados para a memória automaticamente
O mesmo serve para o MBR. Apenas um bit de controle é necessário, que é enviado para a saída do MBR, controlando se a informação nele contida irá ou não para a memória
As operações de leitura e escrita na memória continuam sendo comandadas pelos bits RD e WR descritos anteriormente
99
Ótimo, precisávamos de um registrador de 64 bits para controle, e agora conseguimos reduzir este tamanho para 24
Superficialmente, o que nós temos até então é:
Memória principal
Processador
Controle(24 bits)
N2 N0
N1
MAR
MBR
endereços
dados
controle (leit/esc)
Podemos imaginar N0 como um pobre estagiário, que trabalha e executa ordens
O controle, N1, seria o chefe, quem dá as ordens, através da busca das instruções e dos recursos presentes na memória principal
Aos poucos, iremos desvendaro que está por trás da nuvemno slide anterior
100
101
E nosso proces-sador ficou assim:
ULA
DES
LA LB
MAR
MBR
AMUX
Barramento A
Barramento B
Barramento C
16 registradores
A
B
C
4
4
4
1
16
16
17
1
1
1
2
2
1
1
2
Controle
01234..........
23
bits
RD/WR
102
Vamos dessa vez utilizar como exemplo a instrução R1 ← R1 + R2, para ilustrar como ficam os bits de controle durante o processo
A B C EnC AMUX ULA RD WR MAR MBR DES LA LB
103
Chamaremos o momento inicial de t₀ Os registradores R1 e R2 ficam nas posições 3 e 4,
respectivamente, lembrando que o primeiro registrador, PC, fica na posição 0. O valor em R1 será jogado no barramento A, e o valor de R2 no barramento B; logo, A = 0011 e B = 0100
O resultado será armazenado em R1, então o controle do barramento C deve também ser 0011
104
Obviamente, no início do processo ainda não podemos gravar nada no destino, então EnC tem de estar “desativado”
Neste momento, não há problemas em especificar qual dos 2 dados (vindos do MBR ou do latch A) o AMUX irá direcionar para a ULA. Sabemos que serão dados vindos de latch A, então o bit de controle para AMUX será 0
Já vimos que a combinação do controle para que a ULA opere uma soma é 00
105
Nada está sendo lido ou escrito na memória (RD = 0 e WR = 0)
MAR não receberá nenhum endereço de memória
MBR também não está realizando nenhuma atividade em t₀
Também já vimos que a combinação que faz com que o deslocador retorne a própria entrada é 00
As entradas dos latches A e B devem estar desabilitadas, pois as informações ainda não foram transferidas para os barramentos A e B
106
Temos então, no momento t₀:
A B C EnC AMUX ULA RD WR MAR MBR DES LA LB
t₀ 0011 0100 0011 0 0 00 0 0 0 000 00 0 0
107
Após um tempo para busca dos dados, tь, os únicos controles que se modificam são aqueles dos latches
Isso porque, agora que os dados já foram jogados aos barramentos, os latches precisam recebê-los, quando estáveis, para que sejam transferidos à ULA
Vamos considerar t₁ = t₀ + tь
108
Temos então, no momento t₁ :
A B C EnC AMUX ULA RD WR MAR MBR DES LA LB
t₀ 0011 0100 0011 0 0 00 0 0 0 000 00 0 0
t₁ 0011 0100 0011 0 0 00 0 0 0 000 00 1 1
109
Após um tempo de execução, tє, os latches precisam voltar a ser 0, para que nada presente nos barramentos A e B sobrescreva os dados armazenados nos latches, já que não sabemos se eles virão a ser ainda necessários para a ULA futuramente
Além disso, se queremos gravar o resultado de volta em R1, EnC precisa agora passar a ser 1
O resto permanece inalterado Agora consideremos t₂ = t₀ + tь + tє
110
Temos então, no momento t₂ :
A B C EnC AMUX ULA RD WR MAR MBR DES LA LB
t₀ 0011 0100 0011 0 0 00 0 0 0 000 00 0 0
t₁ 0011 0100 0011 0 0 00 0 0 0 000 00 1 1
t₂ 0011 0100 0011 1 0 00 0 0 0 000 00 0 0
111
Mas então, é necessário ler toda a sequência de bits de controle 3 vezes para uma simples operação de soma?
Vimos pela tabela que pouca coisa se altera durante o tempo que decorre desde a leitura dos dados nos registradores até o armazenamento do resultado em um deles
Então, deve haver uma maneira maisinteligente e menos custosa deexecutar instruções. Afinal,tempo é sempre precioso
112
Relógio (clock) Este é outro dispositivo importante, que emite
uma sequência de pulsos periódicos que controlam alguns circuitos da máquina
Se os pulsos são periódicos, quer dizer que possuem uma determinada frequência
O período de cada pulsação define o ciclo da máquina
A máquina realiza um conjunto de atividades durante um ciclo
Representação:
Marcador de frequência
Ciclo
Pulsos
113
Agora, podemos conservar todos os bits que se mantiveram inalterados na tabela, deixando por conta do relógio as alterações necessárias
Por exemplo, sabemos que o bit EnC está diretamente relacionado ao bit HE dos registradores
Os pulsos enviados pelo clock definem então o momento exato em que a entrada do registrador deve ser habilitada, caso se deseje armazenar o resultado de algum cálculo em um registrador
Isto pode ser feito através de um circuito bastante simples:
CK
EnC
HE
114
Com isso, já temos tudo de que precisamos para começar a ver o que está por dentro da nuvem que havia em N1
Em outras palavras, vamos agora entender como funciona toda a máquina de controle do processador e como ela executa o microprograma
Microprogramas são sequências de instruções (em binário, evidentemente) que controlam o funcionamento de cada componente em N0
Cada instrução de um microprograma é executada em um ciclo
115
O processamento de uma microinstrução se resume a: Busca da instrução Identificação da instrução “Execução”, entre aspas porque engloba na verdade:
Busca dos operandos Operação Armazenamento do resultado
Temos então 5 atividades que são realizadas pelo processador durante um ciclo
116
Dentre as 5 atividades listadas, o tempo necessário para a segunda (identificação de uma instrução) será quase nulo na nossa abordagem
Isto porque teremos na nossa arquitetura de controle (N1) um registrador especial chamado MIR (MicroInstruction Register), para onde cada microinstrução será carregada
O MIR já “entende” o significado dos bits de uma instrução de acordo com a posição de cada um
MIR
117
Visto isso, o relógio que utilizaremos poderá ser dividido em 4 subciclos:
O relógio terá 4 saídas, das quais 3 possuem
atrasos. Isso faz com que sejam gerados pulsos em momentos diferentes para cada saída
Cada uma dessas saídas consiste em um subciclo, como vemos na representação acima
Atraso
Atraso
Atraso
1 ciclo1 subciclo
118
Teremos também em N1 uma memória, chamada memória de controle, onde são armazenadas e de onde serão lidas as microinstruções
Nossa memória de controle poderá armazenar, no máximo, 256 instruções, cada uma com 32 bits
Se é no MIR onde cada instrução será carregada, concluímos que a largura do MIR será de 32 bits
MIR
Memória de Controle
Carrega instrução
119
Toda memória precisa estar ligada a um MAR e um MBR, e com a memória de controle não é diferente
Portanto, vamos adicionar um registrador MAR, que chamaremos de MPC (MicroProgram Counter), cuja função é encontrar a próxima instrução do microprograma a ir para o MIR
MIR
Memória de Controle
MPC
Recebe endereço da próxima instrução
Envia endereço da próxima instrução
120
O MBR da memória de controlejá está em N1... ...é o próprio MIR!
Mas precisamos voltar a falar do MPC. De onde ele recebe a próxima microinstrução?
Podemos supor que as instruções são executadas sequencialmente. Neste caso, basta incrementar o endereço atual, e teremos o endereço da próxima
Precisamos então de uma componente simples, mas que ainda não tínhamos visto
121
Incrementador Circuito relativamente simples de poucas portas
lógicas que, como o nome já diz, recebe um número binário como entrada e retorna o seu sucessor
O número binário de entrada, no incrementador que usaremos no nosso exemplo, será composto por 8 bits
Representação:
INC
Temos até agora:
Visivelmente, temos um problema MPC precisa ser controlado para
não ficar o tempo todo selecio-nando endereços na memória!
122
MIR
Memória de Controle
MPCINC
HOUSTON, WE HAVE A PROBLEM
123
Vamos inserir agora o relógio, que será uma componente fundamental para todo o processador, tanto em N0 quanto em N1
Por enquanto, não falaremos da atuação do relógio em N0, e já podemos ligá-lo a duas das componentes de N1 que temos até agora:
MIR
Memória de Controle
MPCINC
Atraso
Atraso
Atraso
1
2
3
4
124
Fazendo isso, permitimos que uma instrução seja carregada da memória para o MIR apenas no início do ciclo (subciclo 1), que é quando a instrução começará a ser processada
Além disso, permitimos que a próxima instrução seja selecionada na memória pelo MPC apenas ao final de um ciclo (subciclo 4), para que seja recebida pelo MIR no início do ciclo seguinte
Note que o MIR estará desabilitado e não irá mudar durante os subciclos 2, 3 e 4. O mesmo vale para o MPC durante os subciclos 1, 2 e 3
125
Nosso nível N1 está quase concluído, mas precisamos estudar o interior do MIR antes de continuar
Você certamente achou estranho quando dissemos que o MIR entende a função de cada bit de uma instrução apenas pela posição. Observe:
ULA
DES
MBR
MAR
RD
WR
EnC
CAMUX
B A
MIR
126
No MIR, os bits da instrução que ele recebe se encaixam em cada uma das partes da figura
Cada divisão do MIR serve para especificar o destino em N0 de cada bit. Por exemplo: o bit mais à esquerda será o controle de AMUX; do bloco DES saem os dois bits de controle para o deslocador; e assim por diante
Não são mais necessários bits de controle para os latches. Já que eles possuem um momento certo para serem ativados e este é o mesmo em todos os ciclos, podemos deixar os latches por conta do relógio
Para continuar a montagem do interpretador de microinstruções, precisamos primeiramente do que está por trás de
127
Alguns slides atrás, fizemos uma suposição de que as instruções do microprograma seriam processadas sequencialmente. Porém, nem sempre isso acontece
É comum que ocorram desvios de endereços durante o processamento, e por isso precisamos reservar bits em uma instrução que especifiquem o endereço da próxima, para quando for necessário que esse desvio ocorra
Já podemos substituir por ADDR, conjunto de bits que representam cada endereço da memória de controle
Se nossa memória de controle armazena até 256 instruções (= 2⁸), ADDR deve ser composto por 8 bits para poder representar todos os endereços
128
. . . .
Memória de Controle
MPCINC
Atraso
Atraso
Atraso
1
2
3
4
ADDR
Ops, o problema do conflito de novo Ainda se lembra da solução?
129
. . . .
Memória de Controle
MPCINC
Atraso
Atraso
Atraso
1
2
3
4
ADDR
Basta adicionar um multiplexador Chamaremos este de MMUX
MMUX
130
É bastante comum que esses desvios de endereços sejam condicionais
Por exemplo, voltar ao primeiro endereço da memória de controle se uma condição X for satisfeita. Senão, selecionar o endereço seguinte
Isto nos leva a revelar o que há por trás de no nosso MIR: um par de bits, o qual chamaremos de COND, que determina se ocorrerá desvio ou se o endereço da próxima instrução será simplesmente o endereço atual + 1
Se você entendeu isso, então você pode concluir que COND será o controle de MMUX. Ou pelo menos parte dele, como veremos mais adiante
131
Lógica de microssequenciamento Diferente de todos os circuitos que apresentamos
até aqui, este foi projetado para uma única situação, bem específica
Ele recebe da ULA informações sobre o resultado de uma operação: um sinal N que diz se foi negativo e um sinal Z que diz se foi igual a 0
Recebe também o par de bits COND do MIR Enfim, sabendo se o resultado da operação foi
positivo, nulo ou negativo e sabendo a condição de desvio da microinstrução atual, podemos indicar se o endereço da próxima microinstrução será simplesmente o atual + 1 ou algum outro
132
Lógica de microssequenciamento Talvez seja uma explicação confusa. Podemos clareá-
la com um exemplo prático:
Por que o trecho de programa acima é válido? Temos todas as informações necessárias para decidir
se x será ou não retornado: sabemos qual a condição para retornar x, e sabemos se x satisfaz a condição (se x é 5, então x é maior que 0)
A saída será o bit de controle para o nosso MMUX Representação:
x = 5;if (x > 0)
return x;
LMS
133
Memória de Controle
MPCINC
Atraso
Atraso
Atraso
1
2
3
4
MUX
AMUX
COND
N1
N0
LMS
ULANZ
Controle
Controle
Temos enfim:
ULA . . . . .
ADDR
134
Agora, convencionando os significados dos bits COND: 00 = não desviar; a próxima instrução estará no endereço
seguinte na sequência de instruções 01 = desviar para o endereço em ADDR se N = 1, isto é, se
o resultado da operação realizada pela ULA for negativo 10 = desviar para o endereço em ADDR se Z = 1, isto é, se
o resultado for 0 11 = desviar independente do resultado da ULA
O sinal de controle de MMUX é resultado de R.N + L.Z + L.R (L é o bit à esquerda, e R é o bit à direita no par COND)
Relembrando: ‘+’ é o operador lógico OR e ‘ . ’ é AND
135
Nosso processador completo, incluindo N0 e N1, fica assim:
Figura retirada e adaptada do livro Organização Estruturada de Computadores, de Andrew S. Tanenbaum (p. 140)
136
Para concluir, falta apenas falar sobre as ligações do relógio com as componentes de N0, que ainda não havíamos visto
Para isto, vamos supor um microprograma qualquer que interpreta a soma de x + y, que foi executada com x recebendo o valor 3 e y recebendo o valor 5
Para simplificar o exemplo, escolheremos o ciclo em que a soma será efetuada. Isto significa que as constantes 3 e 5 já foram buscadas na memória e armazenadas nos registradores (suponhamos R1 e R2), e está pré-determinado que o resultado será armazenado em R3
Assim, a microinstrução deste ciclo será r3 := r1 + r2 (o símbolo “:=“ denota atribuição)
137
Para conseguir passar esta microinstrução para sua forma real (binária), vamos relembrar as divisões do MIR:
Depois, vamos analisar quais os bits de controle necessários para cada um desses campos e, dessa forma, teremos nossa instrução em bits
ULA
DES
MBR
MAR
RD
WR
EnC
CCOND
AMUX
B A
ADDR
138
Sabemos que ambos os operandos virão dos latches, já que não buscamos nada do MBR. Para que AMUX direcione à ULA a informação contida no latch A, o controle deve ser 0
ULA
DES
MBR
MAR
RD
WR
EnC
CCOND
AMUX
B A
ADDR
Não há desvios nesta instrução, então COND = 00 A operação é de soma. Esta operação é realizada
pela ULA quando o controle é igual a 00
0 00 00
139
Nada será feito além da soma, então o controle do deslocador precisa ser 00 para que sua saída seja igual à entrada
ULA
DES
MBR
MAR
RD
WR
EnC
CCOND
AMUX
B A
ADDR
Nada sairá de MBR para a memória ou vice-versa O campo MAR também será 0 já que não enviará
nenhum endereço para a memória principal
0 00 00 00 0 0
140
Nada será lido da memória neste ciclo (RD = 0)...
ULA
DES
MBR
MAR
RD
WR
EnC
CCOND
AMUX
B A
ADDR
...nem escrito (WR = 0) O bit EnC deve ser 1 porque neste ciclo haverá
armazenamento de um valor (o resultado da soma está sendo atribuído a R3 na microinstrução). Evidentemente, o relógio irá ditar o momento certo do armazenamento
0 00 00 00 0 0 0 0 1
141
Lembra-se dos nossos registradores?
ULA
DES
MBR
MAR
RD
WR
EnC
CCOND
AMUX
B A
ADDR
Começando da posição 0, R1, quecontém o primeiro operando, está na posição 3. Logo, A = 0011 (3 na base 2)
R2, que contém o segundo operando,está na posição 4. Logo, B = 0100
0 00 00 00 0 0 0 0 1
PCIRAC.......
R1R2R3
Enfim, R3, destino do resultado, está na posição 5. Logo, C = 0101
0101 0100 0011
0
1
2
3
4
5
142
Agora só nos resta o campo ADDR. Nossa microinstrução contém uma simples soma, sem desvio de endereços. Então, podemos atribuir qualquer valor para ADDR, já que ele será ignorado. Sendo assim, vamos optar por todos os bits sendo 0
ULA
DES
MBR
MAR
RD
WR
EnC
CCOND
AMUX
B A
ADDR
0 00 00 00 0 0 0 0 1 0101 0100 0011 00000000
Enfim, nossa microinstrução na forma binária é: 00000000000101010100001100000000
143
Agora, podemos começar a processar a instrução No subciclo 1, o relógio ativa apenas o MIR,
porque estamos ainda na etapa da busca da instrução
Vimos que a instrução é automaticamente identificada no MIR e não leva praticamente tempo algum. Consideramos então que a identificação também ocorre no subciclo 1
0 00 00 00 0 0 0 0 1 0101 0100 0011 00000000
Memória de Controle
MIR
.
.
.
.
.
144
Agora, podemos começar a processar a instrução No subciclo 1, o relógio ativa apenas o MIR,
porque estamos ainda na etapa da busca da instrução
Vimos que a instrução é automaticamente identificada no MIR e não leva praticamente tempo algum. Consideramos então que a identificação também ocorre no subciclo 1
0 00 00 00 0 0 0 0 1 0101 0100 0011 00000000
Memória de Controle
MIR
.
.
.
.
.
145
No subciclo 2, os bits dos campos A e B do MIR serão enviados aos decodificadores, para habilitar as saídas dos registradores R1 e R2
Com isso, os valores 3 e 5 são jogados aos barramentos A e B, respectivamente
Neste subciclo, o relógio ativa os latches, para que possam receber esses valores
Retomaremos a figura do livro Organização Estruturada de Computadores para mostrar o caminho dos dados durante este subciclo
146
3 5
147
O subciclo 3 é o intervalo em que ULA e deslocador irão operar sobre os dados recebidos
Aqui seria também o momento certo para que o MAR fosse carregado, se fosse necessário. Mas a microinstrução deste ciclo não envolve essa necessidade, e por isso tivemos o bit 0 na parte do MIR destinada ao MAR
5 + 3
---
8
8
00 (soma)
00 (saída = entrada)
148
E finalmente, é no subciclo 4 em que ocorre o armazenamento do resultado em um dos nossos 16 registradores, quando a instrução assim determina
Nossa microinstrução r3 := r1 + r2 atribui o resultado da soma a R3, o que significa que temos, sim, que armazenar dados em um dos 16 durante este ciclo
O valor 8, neste instante, está no barramento C Já vimos que EnC é 1 durante todo o ciclo, pois é um
dos sinais de controle fornecidos pelo MIR. Cabe ao relógio habilitar a entrada de R3 no momento certo, que é o subciclo 4 para qualquer microinstrução
Agora que 8 está em R3, podemos começar um novo ciclo com uma nova microinstrução, caso ela exista
149
Podemos dividir o nosso MIR em 2 conceitos:
ADDR é formado por 8 bits, e A, B e C por mais 4 cada. Ou seja, 20 bits compõem os operandos
Os outros 12 do MIR compõem então o código da operação. Isto quer dizer que temos 2¹² (= 4096) possíveis instruções diferentes
ULA
DES
MBR
MAR
RD
WR
EnC
CCOND
AMUX
B A
ADDR
OPCODE(determina a operação da microinstrução)
Operandos
150
Porém, queremos economizarnovamente. Será que é possível?
Com uma análise mais cuidadosa,veremos que podemos reduzir de4096 para 2048 possibilidades
Para iniciar nossa “eco-nomia”, precisamos ter em mente que alguns pares de bits do MIR não admitem todas as 4 combinações (00, 01, 10 e 11)
151
Mais especificamente, o par de bits RD e WR não admite a combinação 11, já que é impossível que aconteçam simultaneamente operações de leitura e escrita na memória
Vamos raciocinar: tínhamos 2¹² possibilidades
Quantas possibilidades teríamos se o par RD/WR apenas admitisse a combinação 11, isto é, se RD fosse sempre 1 e WR fosse sempre 1?
2 2 2 2 2 2 2 2 2 2 2 20 ou 1
= 2 x 2 x 2 x 2 x 2 x 2 x 2 x 2 x 2 x 2 x 2 x 2 = 2¹²
2 2 2 2 2 2 2 2 2 1 1 2
= 2 x 2 x 2 x 2 x 2 x 2 x 2 x 2 x 2 x 1 x 1 x 2 = 2¹⁰
152
Daí, temos que o nosso total de possibilidades agora é 2¹² - 2¹⁰, isto é, 4096 - 1024 = 3072
Podemos usar um raciocínio análogo para o deslocador, que nunca receberá a combinação 11
Teremos 3072 - 1024 = 2048 microinstruções Assim, já reduzimos as possibilidades de
instruções pela metade. Poderíamos levar em conta outras restrições, mas chega de ser pão duro e vamos voltar ao que interessa
153
Pilhas
Você já deve saber que uma pilhaé uma estrutura de dados queconsiste em um amontoado de valo-res (empilhados, daí o nome)
Pilhas serão importantes na execuçãode programas. Veremos a seguir comoelas são utilizadas
154
Toda pilha possui duas operações básicas: PUSH, que consiste em adicionar um valor na pilha; e POP, que retira da pilha ovalor presente no topo
Para a nossa arquitetura, pilhas serãoblocos contíguos presentes na memó-ria principal que conterão dados a serem processados, com um ponteiro SP (Stack Pointer) que indicará o endereço onde está o topo da pilha
À medida que descemos pela memória, os endereços aumentam. Ou seja, sempre que adicionarmos um valor na pilha (PUSH), o ponteiro SP apontará para o endereço atual – 1, um endereço acima
O contrário acontece para a operação POP
155
A pilha do exemplo a seguir começa no endereço 1000 da memória, e seu topo está em 996
.
.
.
.
.
.
.
.
.
.
.
1000
999
998
997
996
4
9
7
2
5
SP
156
Fazendo PUSH 3, o valor 3 será acrescentado na pilha, e o topo será o endereço 996 – 1 = 995
.
.
.
.
.
.
.
.
.
.
.
1000
999
998
997
996
4
9
7
2
5
SP
995 3
157
Por outro lado, se tivéssemos feito POP, o valor 9 passaria a ser lixo e o novo topo estaria em 996 + 1 = 997
.
.
.
.
.
.
.
.
.
.
.
1000
999
998
997
996
4
9
7
2
5
SP
158
Algumas linguagens de programação são organizadas em blocos (escopos)
Em Java, por exemplo, o escopo de uma classe são todos os procedimentos existentes naquela classe, e no escopo de cada procedimento existem todas as operações realizadas por ele, inclusive eventuais chamadas a outros procedimentos
159
Para essas linguagens, é interessante utilizar pilhas durante a execução de um programa para liberar o espaço ocupado pelas variáveis locais de um procedimento, quando ele é finalizado
Variáveis locais são variáveis utilizadas e conhecidas exclusivamente nos procedimentos onde foram inicializadas, e no nível de macroprogramação são geralmente carregadas nas pilhas
Já as variáveis globais são variáveis conhecidas em todo o programa e a princípio estão presentes em endereços da memória que não fazem parte da pilha de um procedimento em particular
160
Além das operações PUSH e POP, outra operação útil para pilhas é mover arbitrariamente o ponteiro SP, sem inserir dados inicialmente
Isto é útil para quando a execução entrar em um procedimento que utilizará variáveis locais, de maneira que SP será decrementado (topo subirá) para reservar espaço para essas variáveis
Em Pascal, isso fica fácil de ser visto, já que as variáveis locais são inicializadas antes da palavra begin, que indica o começo do procedimento
O exemplo a seguir mostra o começo do método de ordenação BubbleSort, em Pascal
161
procedure BubbleSort(var A: ArrayType);
var i, j: integer;begin.
.
.
Repare que as variáveis i e j são inicializadas. Em seguida, a palavra begin indica o começo das operações do procedimento – é neste ponto onde SP é decrementado em 2
A pilha agora ocupa mais 2 espaços: os espaços de i e j, que eventualmente receberão valores durante a execução do procedimento
E se o topo da pilha atingir endereços que já estão sendo usados pelo programa?
162
Familiar?
Agora vamos supor uma função, também em Pascal, que recebe duas entradas e retorna como resultado o produto entre elas
Perceba como fica sua pilha de execução
163
function produto(a, b: integer): integer;var p, j: integer;begin
if (a = 0) or (b = 0) thenproduto := 0
elsebeginp := 0;for j := 1 to a do
p := p + b;produto := p;end
end;
ab
Quando a função é chamada no programa, já temos na pilha as variáveis de entrada, e precisamos também do endereço de retorno, para que o processador saiba para onde voltar quando a função terminar
end. ret.SP
164
function produto(a, b: integer): integer;var p, j: integer;begin
if (a = 0) or (b = 0) thenproduto := 0
elsebeginp := 0;for j := 1 to a do
p := p + b;produto := p;end
end;
ab
end. ret.pjSP
Já ao final desta linha, precisamos de mais 2 para as variáveis locais p e j
165
function produto(a, b: integer): integer;var p, j: integer;begin
if (a = 0) or (b = 0) thenproduto := 0
elsebeginp := 0;for j := 1 to a do
p := p + b;produto := p;end
end;
ab
end. ret.
Quando a função termina, os espaços das variáveis locais são liberados. Em outras palavras, SP é incrementado em 2, voltando a ser o endereço de retorno. Este endereço dirá onde a execução estava quando chamou a função produto, e ela continuará a partir daquele ponto
SP
166
Com apenas estas descrições, fica aimpressão de que a função foi inútil,já que a pilha guardou apenas seusvalores de entrada e manipulou as variáveis locais
Mas é óbvio que isto não é verdade. De que maneira então foram usadas as variáveisa e b para chegar até o resultado final,e onde ele foi parar?
Todos os resultados de operações intermediárias foram sendo armazenados no nosso registrador AC (Accumulator). O mesmo vale para o resultado final da função, que pode ser apenas um valor intermediário para o resto do programa
167
O endereço de retorno está relacionado a outro registrador, o Program Counter
Quando a função produto termina, o endereço de retorno é desempilhado e salvo em PC
Caso você não se lembre, AC e PC são dois dos nossos 16 registradores conectados aos barramentos A, B e C em N0
Isso nos dá bagagem suficiente para falar sobre o nível de macroprogramação
168
Macroprogramação Vimos que existem milhares de possibilidades de
microinstruções diferentes Surge a necessidade de haver instruções em um
nível mais alto, para consequentemente reduzir o conjunto de instruções de forma significativa
Veremos no slide seguinte uma tabela, mais uma vez do livro Organização Estruturada de Computadores, com todas as macroinstruções da nossa máquina e suas descrições
Repare como diminuímos de mais de 2 mil para menos de 25 instruções
169
170
Antes de entender melhor a tabela, é importante ressaltar que seus bits não devem nunca ser confundidos com os bits das microinstruções! Até porque microinstruções são formadas por 32 bits, enquanto as macro possuem 16 apenas
Dito isto, perceba que da 1ª até a 15ª instrução, nunca se repete uma combinação para os 4 primeiros bits. Isto quer dizer que é possível identificar instruções que não comecem por 1111 apenas pelos 4 primeiros bits
Porém, quando a instrução começa por 1111, é necessário analisar os bits seguintes
Diferentemente das microinstruções, que tinham um número fixo para OPCODE e outro para os operandos, a parte de OPCODE varia nas macro
A instrução LODD, por exemplo, possui 4 bits de operação, 0000, enquanto todos os outros representam seu operando. Já as instruções que começam por 1111 precisam de mais bits OPCODE para que possam ser diferenciadas entre si
171
172
Para ilustrar como é feita a identificação das macroinstruções, precisamos primeiro tornar aqueles nossos 16 registradores um pouco mais complexos
Além de PC, IR e AC, precisamos de mais registradores com funções especiais para que seja possível implementar um microprograma que interprete nossas macroinstruções corretamente
Mais uma vez, trabalharemos em cima de uma figura do livro Organizações Estruturadas de Computadores, que mostra os registradores adequadamente nomeados
173
O registrador SP armazena o endereço do topo da pilha
TIR armazena uma cópia temporária da macroinstrução que está sendo executada, ou seja, uma cópia de IR. É usado para decodificar o OPCODE
Os 3 registradores que seguem contém as constantes 0, +1 e -1
AMASK contém a chamada máscara de endereços e é usado para separar bits OPCODE dos bits de endereço, através da operação AND do seu conteúdo com a macroinstrução da qual se quer separar os bits
SMASK é útil nas instruções INSP e DESP, que contém 8 bits de operandos, pois a operação AND de seu conteúdo com a macroinstrução isolará esses 8 bits
Os demais registradores permanecem sem funções específicas
174
É preciso convencionar os nomes de algumas operações feitas em microinstruções
Para as operações da ULA, utilizaremos a + b para soma, band(a, b) para AND, a para retornar a própria entrada e inv(a) para retornar o inverso do primeiro operando
Para operações do deslocador: lshift(a) desloca a entrada 1 bit para a esquerda, e rshift(a) faz o oposto
Desvios condicionais serão representados por if b then goto addr, que significa: se o bit b for 1, vá para o endereço addr
No nosso microprograma, b será N ou Z, os dois bits da ULA que indicam se o resultado foi menor que 0 ou igual a 0, respectivamente
175
Veja a seguir quais valores cada conjunto de bits assume no MIR para os seguintes exemplos de microinstruções, em uma nova tabela do livro:
Dito tudo isso, podemos ver um microprograma que interpreta macroinstruções
176
177
O microprograma começa com um loop principal, que só termina quando acabar o macroprograma, isto é, quando não mais houver macroinstruções a serem interpretadas
Na linha 0, o MAR recebe o conteúdo de PC, que é o endereço da macroinstrução a ser interpretada. Também nesta linha, RD é ativado, para que a macroinstrução desejada comece a ser lida na memória a partir do endereço do MAR
Na linha 1, o microprograma aproveita para incrementar o conteúdo de PC, já que isso alguma hora teria que acontecer. Se isto não fosse feito, a linha 1 seria desperdiçada, porque a macroinstrução ainda não chegou até o MBR. Pelo mesmo motivo, RD continua ativado, pois ainda estamos lendo a macroinstrução na memória
178
Na linha 2, a macroinstrução em MBR é salva em IR. Aqui ainda há tempo para testar o primeiro bit da instrução, da esquerda para a direita: se este não for 0, já sabemos que a macroinstrução não é nenhuma das 8 primeiras da nossa tabela, então podemos ir para a linha 28 e testar o segundo bit
179
Tanto na linha 28 quanto na linha 3, repare que o teste do desvio condicional é exatamente o mesmo: tir := lshift(ir + ir);
Não é uma percepção trivial à primeira vista, mas o que acontece nesse teste é o deslocamento de dois bits à esquerda do conteúdo de IR
Recordando, deslocar um bit à esquerda em um número binário é o mesmo que multiplicar o seu valor por 2. Fazer ir + ir é equivalente a fazer 2 * ir, ou seja, deslocar um bit à esquerda de ir. Fazer lshift(2 * ir) então é o mesmo que deslocar 2 bits
Porém, lembre-se de que os testes são feitos sempre em função dos bits N e Z liberados pela ULA
180
Vamos voltar a focar nas primeiras linhas do microprograma. Na linha 3 (tir := lshift(ir + ir); if n then goto 19;), a interpretação irá para a linha 19 caso o bit N da saída da ULA seja 1
Isto quer dizer que o teste é feito em cima da instrução vinda de IR deslocada um bit à esquerda, pois o segundo deslocamento à esquerda (feito por lshift) é feito somente no deslocador, e não mais na ULA
Caso N tenha sido 0, passamos à linha 4 (tir := lshift(tir); if n then goto 11;). Aqui, o registrador TIR receberá seu próprio conteúdo deslocado em um bit à esquerda
181
Só que este deslocamento é novamente feito por lshift, e opera em cima do próprio conteúdo de TIR. Isso significa que a ULA receberá tir como um dos operandos e retornará ele mesmo – lembrando que tir já é o conteúdo de IR deslocado 2 bits à esquerda, por causa do que aconteceu na linha 3
Então, se na linha 3 testamos o segundo bit da instrução, na linha 4 testa-se o terceiro, e só depois desse teste o conteúdo de tir é novamente deslocado (lshift(tir))
Na linha 5 (alu := tir; if n then goto 9;), alu é uma pseudovariável que representa que o conteúdo de TIR apenas passará pela ULA para que seja testado seu primeiro bit (4º da instrução original)
182
Na linha 5, se o bit testado for 1, significa que o OPCODE da instrução é 0001, e STOD é a única macroinstrução cujo início é 0001. Por isso desviamos para a linha 9, linha onde STOD é executada em microinstruções
Se o bit testado for 0, o OPCODE é 0000, então basta prosseguir para a linha seguinte, que é onde LODD é executada
Esse processo teste-desvio-execução é a base do microprograma que interpreta macroinstruções
Acompanhe a seguir um trecho, bastante simples, de um programa qualquer
183
se (i ≤ j) faça // ‘i’ e ‘j’ são variáveis quaisquer
m = a[i*2] // ‘a’ é um vetor e ‘m’ uma variável // qualquer
fim se
Vamos expressar este algoritmo em nível demacroprogramação, e depois em microinstruções, para enfim nos despedirmosda nossa máquina-exemplo
184
Macroprograma Serão realizadas 4 operações nesse algoritmo: i ≤ j? i * 2 buscar valor em a[i * 2] m := a[i * 2]
Testar se i ≤ j é o mesmo que testar se i - j ≤ 0 Atenção: ‘i’ e ‘j’ são variáveis globais
185
Macroprograma Serão realizadas 4 operações nesse algoritmo: i ≤ j? i * 2 buscar valor em a[i * 2] m := a[i * 2]
Testar se i ≤ j é o mesmo que testar se i - j ≤ 0 Atenção: ‘i’ e ‘j’ são variáveis globais Com a instrução LODD passando j (entenda as variáveis passadas no macroprograma como o endereço onde essas variáveis estão, e não seus respectivos valores), armazenamos no registrador AC o valor contido no endereço passado
LODD j
186
Macroprograma Serão realizadas 4 operações nesse algoritmo: i ≤ j? i * 2 buscar valor em a[i * 2] m := a[i * 2]
Testar se i ≤ j é o mesmo que testar se i - j ≤ 0 Atenção: ‘i’ e ‘j’ são variáveis globais Com a instrução LODD passando j (entenda as variáveis passadas no macroprograma como o endereço onde essas variáveis estão, e não seus respectivos valores), armazenamos no registrador AC o valor contido no endereço passado
LODD j
Precisamos agora subtrair do valor de i. Com a instrução SUBD, o resultado já será armazenado em AC
SUBD i
187
Macroprograma Serão realizadas 4 operações nesse algoritmo: i ≤ j? i * 2 buscar valor em a[i * 2] m := a[i * 2]
Testar se i ≤ j é o mesmo que testar se i - j ≤ 0 Atenção: ‘i’ e ‘j’ são variáveis globais Com a instrução LODD passando j (entenda as variáveis passadas no macroprograma como o endereço onde essas variáveis estão, e não seus respectivos valores), armazenamos no registrador AC o valor contido no endereço passado
LODD j
Precisamos agora subtrair do valor de i. Com a instrução SUBD, o resultado já será armazenado em AC
SUBD i
Hora de implementar o desvio condicional: caso o conteúdo de AC seja negativo (j > i), desviamos para o final do ‘se’ do algoritmo. Para isso, criaremos um label “saida”
JNEG saida
saida(continuaçãodo programa)
.
.
.
.
188
Macroprograma Agora, carregamos o endereço do primeiro elemento do vetor a no registrador AC. Se fizéssemos LODD a, teríamos o valor de a[0] no acumulador, e não é o que queremos
LODD jSUBD iJNEG saida
saida(continuaçãodo programa)
.
.
.
.
.
.
LOCO a
189
Macroprograma Agora, carregamos o endereço do primeiro elemento do vetor a no registrador AC. Se fizéssemos LODD a, teríamos o valor de a[0] no acumulador, e não é o que queremos
LODD jSUBD iJNEG saida
saida(continuaçãodo programa)
.
.
.
.
.
.
LOCO a Quisemos o endereço de a[0] porque assim podemos encontrar a[i * 2]. Já que i * 2 = i + i, fazemos 0 + i...
ADDD i
190
Macroprograma Agora, carregamos o endereço do primeiro elemento do vetor a no registrador AC. Se fizéssemos LODD a, teríamos o valor de a[0] no acumulador, e não é o que queremos
LODD jSUBD iJNEG saida
saida(continuaçãodo programa)
.
.
.
.
.
.
LOCO a Quisemos o endereço de a[0] porque assim podemos encontrar a[i * 2]. Já que i * 2 = i + i, fazemos 0 + i...
ADDD i
...+ i, e temos em AC o endereço correto do (i * 2)-ésimo elemento do vetor a
ADDD i
191
Macroprograma Agora, carregamos o endereço do primeiro elemento do vetor a no registrador AC. Se fizéssemos LODD a, teríamos o valor de a[0] no acumulador, e não é o que queremos
LODD jSUBD iJNEG saida
saida(continuaçãodo programa)
.
.
.
.
.
.
LOCO a Quisemos o endereço de a[0] porque assim podemos encontrar a[i * 2]. Já que i * 2 = i + i, fazemos 0 + i...
ADDD i
...+ i, e temos em AC o endereço correto do (i * 2)-ésimo elemento do vetor a
ADDD i
Aqui vem o problema: queremos fazer ac := m[ac], mas não temos uma instrução que o faça. Temos como fazer, porém, m[sp] := m[ac], isto é, colocar na pilha de execução o valor contido no endereço presente em AC
PSHI
192
Macroprograma Agora, carregamos o endereço do primeiro elemento do vetor a no registrador AC. Se fizéssemos LODD a, teríamos o valor de a[0] no acumulador, e não é o que queremos
LODD jSUBD iJNEG saida
saida(continuaçãodo programa)
.
.
.
.
.
.
LOCO a Quisemos o endereço de a[0] porque assim podemos encontrar a[i * 2]. Já que i * 2 = i + i, fazemos 0 + i...
ADDD i
...+ i, e temos em AC o endereço correto do (i * 2)-ésimo elemento do vetor a
ADDD i
Aqui vem o problema: queremos fazer ac := m[ac], mas não temos uma instrução que o faça. Temos como fazer, porém, m[sp] := m[ac], isto é, colocar na pilha de execução o valor contido no endereço presente em AC
PSHI
...e depois armazenar este valor em AC (ac := m[sp])
POP
193
Macroprograma Agora, carregamos o endereço do primeiro elemento do vetor a no registrador AC. Se fizéssemos LODD a, teríamos o valor de a[0] no acumulador, e não é o que queremos
LODD jSUBD iJNEG saida
saida(continuaçãodo programa)
.
.
.
.
.
.
LOCO a Quisemos o endereço de a[0] porque assim podemos encontrar a[i * 2]. Já que i * 2 = i + i, fazemos 0 + i...
ADDD i
...+ i, e temos em AC o endereço correto do (i * 2)-ésimo elemento do vetor a
ADDD i
Aqui vem o problema: queremos fazer ac := m[ac], mas não temos uma instrução que o faça. Temos como fazer, porém, m[sp] := m[ac], isto é, colocar na pilha de execução o valor contido no endereço presente em AC
PSHI
...e depois armazenar este valor em AC (ac := m[sp])
POP
Finalmente, armazenar o valor em AC no endereço da memória onde está a variável m
STOD m
194
Com o macroprograma e com o que vimos até aqui sobre microinstruções, fica fácil entender como é o nosso algoritmo em microprograma
Antes, precisamos apenas esclarecer como funciona a subtração
O oposto de um número x é -x, e a codificação do oposto de um número binário é o seu inverso + 1
Formalizando: x - y = x + (-y) = x + (y + 1) Lembre-se também: usaremos &v quando
quisermos o endereço de uma variável v
195
mar := &i; rd; //MAR recebe o endereço da variável ird; //esperando o valor de i chegar até MBRb := mbr; //B é um dos resgistradores sem função
definidamar := &j; rd;ac := b + 1; rd; //armazenando i + 1 em AC. Falta somar com j a := inv(mbr); //valor de j chegou ao MBR. A recebe o
inversoac := ac + a; if n goto faça; //se i + j + 1 < 0, não saia do algoritmoalu := ac; if z goto faça; //se essa soma for igual a 0, também não saiagoto saida;
faça b := lshift(b); //multiplicando por 2 o conteúdo de Bac := &a; //AC recebe o endereço de a[0]ac := ac + b; //AC recebe o endereço de a[i*2]mar := ac; rd; //lendo na memória o valor que está em a[i*2]rd;mar := &m; wr; //escreve na variável m o que acabou de ser lidowr;
saida ... //aqui está o que vem após o ‘fim se’ no programa
196
Há apenas uma última observação. Analise estas últimas linhas do microprograma anterior:
mar := ac; rd;rd;mar := &m; wr;wr;
Implicitamente, o que temos é:mar := ac; rd;rd;ac := mbr;mar := &m; wr;mbr := ac; wr;
O acumulador recebe o que foi lido da memória (valor em a[i*2]). Em seguida, indicamos que estamos visando o endereço da variável m para escrita, fazemos o MBR receber o valor em AC e escrevemos esse valor no endereço onde está m
197
Princípios da Microprogramação
Horizontal X Vertical
Passaremos agora a discutir sobre dois conceitos relacionados à microprogramação
Na microprogramação horizontal, a preocupação é exclusivamente com as portas lógicas
Nada de sinais codificados para economia de bits: cada conjunto de sinais que sai do MIR controla de forma direta sua respectiva componente
198
Isto significa que as microinstruções em uma arquitetura assim teriam largura equivalente ao total de sinais de controle recebidos pelas componentes
...mas é claro que estaexplicação não deixounada muito claro
199
Usaremos um exemplo da máquina que projetamos anteriormente. Lembra-se do nosso MIR?
ULA
DES
MBR
MAR
RD
WR
EnC
CCOND
AMUX
B A
ADDR
Os campos A, B e C possuíam 4 bits cada, que passavam por decodificadores onde eram “transformados” em 16
Se tivéssemos usado, exclusivamente, microprogramação horizontal na arquitetura, cada um desses campos teria 16 bits. Isto aumentaria drasticamente a largura do MIR
200
Os campos ULA e DES também ficariam maiores. Os pares 00, 01, 10 e 11 também são codificações, as quais geram um conjunto de 4 bits que identificam qual a operação a ser realizada pela ULA ou pelo deslocador
No caso do deslocador, pode ser gerado um conjunto de 3 bits, já que o deslocador possui apenas 3 funções e o par 11 nunca ocorre
Assim, concluímos que o campo ULA passaria a ter 4 bits e o campo DES, 3 bits
201
É fácil perceber que seguir à riscao conceito de microprogramaçãohorizontal não teria sido a opçãomais inteligente para a nossa máquina,já que sempre optamos por tentar diminuir o tamanho da memória de controle e, assim, do MIR
Mas se quiséssemos ser ainda mais econômicos na largura das microinstruções (e do MIR, consequentemente) da arquitetura que projetamos, poderíamos aplicar o conceito de microprogramação vertical
202
A microprogramação vertical carrega uma ideia rigorosamente oposta à horizontal: encurtar ao máximo a largura das microinstruções, criando o máximo possível de codificações nos sinais
Diminuir o número de bits das microinstruções é geralmente uma ideia interessante. Vamos mostrar como ficaria nossa máquina se seguíssemos este princípio exclusivamente
Nosso MIR, que antes tinha 32 bits de largura, pode passar a ter apenas 12, como veremos a seguir
203
Com apenas 4 bits para OPCODE e 8 para operandos (4 para cada), é possível escrever um microprograma equivalente ao que vimos anteriormente
É claro que as restrições aumentam com esses cortes
Agora, só temos 2 operandos. Como saber onde o resultado será gravado?
A solução é guardar o resultado sempre no mesmo registrador de um dos operandos
OPCODEOPERANDO 1 OPERANDO 2
ADDR (endereço)
4 bits 4 bits 4 bits
204
Antes, podíamos fazer R3 <- R1 + R2. Agora, se queremos de fato guardar em R3 o resultado da soma dos valores de R1 e R2, precisamos fazerR1 <- R1 + R2; e depois R3 <- R1
Por este exemplo já foi possível perceber que o microprograma ficaria mais extenso do que aquele que fizemos anteriormente, mas isso é assunto para mais tarde
A seguir, será apresentada como ficaria nossa máquina em microprogramação vertical. Repare na presença de 3 novos elementos: OP, AND e NZ
205
206
Recordando: os dados de entrada são sempre carregados dos registradores no subciclo 2, e o resultado é salvo em um deles no subciclo 4
Já que agora temos 2 operandos e um deles será ao mesmo tempo um dado de entrada e o destino do resultado, o bloco AND se faz necessário para ativar cada barramento no subciclo correto
Pelo esquema apresentado alguns slides atrás, vimos que os 2 operandos da microinstrução pode também ser usado como um endereço. Isto significa que não podemos ter endereço e operandos em uma mesma microinstrução
207
Para começo de conversa, já podemos com isso adiantar que não é possível testar se o resultado de uma operação da ULA é maior, igual ou menor que 0, em apenas uma microinstrução
Precisamos portanto armazenar os bits N e Z liberados pela ULA, para que eles possam ser avaliados no ciclo seguinte. É aí que entra NZ, que nada mais é do que um registrador que armazena estes dois bits
Resta falar sobre OP, a mais importante das 3 inéditas componentes em nossa nova máquina
208
Para cada combinação de OPCODE (16 possíveis, já que são 4 bits), são gerados 13 bits que controlam a lógica de microssequenciamento (componente usada na avaliação dos bits N e Z), a ULA, o deslocador, o registrador NZ, o AMUX, os registradores MBR e MAR, e os sinais RD e WR; 2 bits para os 3 primeiros e um bit para os outros
O problema é que quem está projetando a máquina precisa construir todo um circuito que gere os bits de controle corretos para cada OPCODE
209
Exemplificando: suponha que as combinações OPCODE para as instruções que iniciam as operações de leitura e escrita (portanto, envolvem a ativação do MAR) sejam 1000 e 1001
Tudo o que sabemos é: A combinação de OPCODE 1000 gera o bit 1 para MAR A combinação de OPCODE 1001 gera o bit 1 para MAR As demais combinações geram o bit 0 para MAR
Temos então uma tabela que contém os bits de entrada e também os bits de saída
210
A B C D S0 0 0 0 00 0 0 1 00 0 1 0 00 0 1 1 00 1 0 0 00 1 0 1 00 1 1 0 00 1 1 1 01 0 0 0 11 0 0 1 11 0 1 0 01 0 1 1 01 1 0 0 01 1 0 1 01 1 1 0 01 1 1 1 0
A, B, C e D seguem a ordem do bit mais significativo para o menos significativo, e S é a saída A partir daí, basta utilizar os conhecimentos da disciplina de Circuitos Digitais para montar um circuito apropriado
Da tabela, obtemos (A.B.C.D) + (A.B.C.D), que pode ser simplificado em A.B.C
Com isso, já temos projetado o circuito que, recebendo os bits de OPCODE, gera o bit de controle apropriado para MAR. Os circuitos que vão gerar os outros 12 bits de controle são construídos utilizando o mesmo raciocínio deste exemplo
Enfim, o bloco OP nada mais é do que o conjunto de todos estes circuitos. Como chegar em cada um deles não é relevante para os nossos estudos
211
Para efeitos de comparação, vamos supor que fosse ser executado em nossa máquina original um microprograma de 80 instruções, lembrando que na máquina original uma microinstrução tinha 32 bits de largura, enquanto na que acabamos de projetar, uma microinstrução possui 12 bits
212
80 instruções
Muito provavelmente,mais de 80 instruções
Como já foi dito, as restrições causadas pela fixação da largura de OPCODE em 4 fazem com que uma função desempenhada por uma microinstrução em nossa primeira máquina necessite de mais de uma microinstrução na nova arquitetura (operar na ULA e depois avaliar o resultado, por exemplo)
Assim, podemos saber qual das duas máquinas será mais econômica através de um cálculo bastante simples
O microprograma que ocupar menos “volume” na memória de controle determinará a resposta
213
Por “volume”, entende-se a largura de cada microinstrução multiplicada pelo total de microinstruções
Na máquina original, ocupou-se um volume de 80 x 32 = 2560 bits. Se fizermos:
80 x 32 > n x 12teremos n < 213, isto é, a máquina que acabamos de projetar será mais econômica caso o microprograma equivalente àquele de 80 instruções tenha menos de 213
E a tendência é que realmente tenha bem menos que 213 microinstruções
214
Então, toda máquina deve ser projetada seguindo à risca o princípio da microprogramação vertical?
Certamente não. Lembre-se de que cada microinstrução é, independente de sua largura, executada no tempo de um ciclo, que é fixo
Quanto mais microinstruções necessárias para executar uma função, mais lenta é a execução. Logo, a nossa primeira máquina é mais cara e mais rápida, enquanto a última é mais lenta, porém mais barata
215
216
Nanoprogramação
Falando em economia, existe ainda outra forma de ocupar menos memória no armazenamento dos microprogramas, que deve ser discutida
Vimos que programas são sequências de instruções. É possível dividi-los em sequências e instruções
Vamos a seguir entender melhor esta jogada
217
Consideremos um microprograma que ocupa completamente esta memória de controle, isto é, possui h instruções de largura w
Podemos dividi-lo em duas partes:
Memória de Controleh
w
218
À esquerda, temos uma memória que armazena números binários que identificam cada instrução
As instruções completas são armazenadas na memória à direita, com a vantagem de cada uma aparecer apenas uma vez
1320125...
h
w
sequência na qualocorre cada instrução,identificada apenaspor um número
ABC...
cada instrução individualmente, todas distintas entre si(n instruções nototal)
219
Novamente, a melhor maneira de clarear tudo é ilustrando com um pequeno exemplo
Imagine o seguinte microprograma:
ABAAACDDBDCAACDB
A equivale a 01100101100101101001 B equivale a 10110110001000011011 C equivale a 11101000110100110000 D equivale a 00010110100101110010
220
Neste exemplo, w = 20 e h = 16, isto é, um total de 16 microinstruções onde a largura de cada uma é de 20 bits, totalizando 320 bits
Vamos agora dividir o microprograma. A parte à esquerda é o microprograma de fato, e à direita temos o nanoprograma, que guarda cada microinstrução diferente
Cada linha do microprograma passa a conter apenas o endereço da nanomemória no qual está a instrução que deveria estar naquele local do microprograma
221
00010000001011110111100000101101
h = 16
w = 20
01100101100101101001101101100010000110111110100011010011000000010110100101110010
00
011011
endereçosABAAACDDBDCAACDB
memória de controle
nanomemória
222
Vimos que cada linha da memória de controle passou a ter 2 bits apenas. Isto porque o microprograma tem, no total, 4 instruções diferentes, fazendo necessários log₂4 = 2 bits para representá-las. A quantidade de linhas é a mesma do microprograma original: 16
Portanto, a memória de controle passou a ter 16 x 2 = 32 bits ocupados
Já a nanomemória possui o total de instruções multiplicado pela largura das mesmas, o que totaliza 80 bits (4 x 20)
Dos 320 bits originais, o microprograma passou a ocupar apenas 32 + 80 = 112 bits!
223
De uma forma geral, temos: o microprograma original ocupa h.w bits a memória de controle após a divisão passa de h.w
para h.log₂n bits ocupados, onde n é o total de instruções diferentes do microprograma
a nanomemória introduzida tem n.w bits ocupados Em outras palavras, a nanoprogramação será
econômica sempre que a seguinte inequação for satisfeita:
h.w > h.log₂n + n.w
224
Observe que n aparece nos dois produtos à direita, sendo determinante na economia dos bits
Isso faz sentido porque, se houver muitas instruções diferentes em relação ao número de linhas no microprograma original, recorrer à nanoprogramação não será eficiente. A nanomemória terá um tamanho próximo ao tamanho que tinha a memória de controle antes da divisão
Tudo o que teríamos então seria uma memória parecida com a anterior, e mais uma outra contendo apenas referências para a nova memória
225
Pipeline
Até agora, nos prendemos muito em economizar espaço na memória e baratear o custo de uma máquina
Mas, e quando quisermos uma máquina mais veloz, isto é, com melhor desempenho?
226
Até aqui, sempre assumimos que o tempo de um ciclo era fixo e pré-definido. E por causa disso, o número de microinstruções a serem executadas definia o tempo gasto na execução do programa
Esquecendo um pouco a memória,vamos focar agora em comoreduzir o tempo gasto na execu-ção das microinstruções
A seguir, veremos o uso dopipeline
227
Até aqui, seguindo o modelo de Von Neumann, vimos que as etapas de execução de uma instrução seguem uma ordem: busca da instrução, identificação, busca dos operandos, execução da operação e armazenamento do resultado
Podemos representar desta forma, onde cada quadrado representa um subciclo:
BI
BOE AR
228
Vimos também que, por este modelo, uma nova microinstrução só pode ser executada após o término da anterior, mesmo que não haja interdependência entre elas
Esquematizando, teríamos:
Como otimizar este processo?
BI
BOE AR1
2
tempo
3
BI
BOE AR
B ...
229
Curiosidades
Henry Ford (1863 – 1947) nasceu nos EUA, fundou a Ford – fábrica de auto- móveis – e defendia for- temente o consumismo Mas nada disso nos interessa
230
Curiosidades
Ford utilizava a chamada “linha de montagem”, que consistia emespecializar seus empregados emfunções definidas O mesmo empregado passava todo o tempo realizando uma única tarefa, mais específica possível e repetidas vezes, de forma que a prática o levasse a diminuir a chance de erros e o fizesse gastar menos tempo para concluir a tarefa
231
Aplicaremos o “fordismo” aos nossos estudos: as componentes deverão estar exercendo suas funções sempre que possível, em vez de ter que esperar o que chamávamos de um ciclo para voltar a trabalhar
O ganho no desempenho é bastante significativo
BI
BOE AR1
2
tempo
3
BI
BOE AR
B ...BI
232
Aplicaremos o “fordismo” aos nossos estudos: as componentes deverão estar exercendo suas funções sempre que possível, em vez de ter que esperar o que chamávamos de um ciclo para voltar a trabalhar
O ganho no desempenho é bastante significativo
BI
BOE AR1
2
tempo
3
BI
BOE AR
BI
BOE AR
233
Dessa forma, assim que a busca de uma instrução for concluída, pode ser iniciada a busca da próxima, já que as componentes especializadas nesta tarefa (no caso, PC, IR e MAR) já estarão livres. E o mesmo serve para identificação, busca de operandos etc
Podemos chamar cada um dos quadrados de um estágio do pipeline
Antes, o tempo total era dado pelo tempo de uma instrução multiplicado pelo total de instruções:
t = n.tinst
234
No pipeline, o tempo de uma instrução é dado pelo número de estágios multiplicado pelo tempo de um estágio:
tinst = ne.te
E, se analisarmos o esquema de execução das instruções no pipeline feito há pouco, teremos o tempo total de execução no pipeline:
tpipe = tinst + (n – 1).te
= (ne + n – 1).te
Com isso, podemos conhecer o speedup, ou seja, o quanto mais rápido é a execução por pipeline em relação à arquitetura anterior
235
O cálculo se dá dividindo o tempo total t do modelo de Von Neumann pelo tempo gasto no pipeline, o que nos dá:
O número de estágios vai ser sempre muito inferior ao número de instruções, portanto podemos ignorar os valores ne e -1 no denominador. No numerador, isso não é possível por ser uma multiplicação, e não uma soma
Passamos a ter então:
236
Com o resultado, inferimos que a execução de um programa é, idealmente, ne vezes mais rápida no pipeline
É fácil visualizar isso: Note que na área entre os pontilhados, 4 tarefas estão sendo executadas ao mesmo tempo, enquanto apenas uma estaria sendo no modelo antigo. Isto porque são 4 estágios
237
Lembre-se de que, em uma aplicação real, o esquema anterior teria centenas de microinstruções. Isto é, a área entre as linhas pontilhadas sempre vai ser a imensa maioria
Mas, como sempre, existem problemas
E é deles que vamos falar a partir de agora
238
Dependência de dados Quando há dependência de dados entre instruções,
algumas componentes precisam se manter ociosas durante alguns ciclos
Isto caracteriza desperdício de tempoa = b + cd = a + h
B E GRIBO
B E GRIBO
Vamos acompanhar a sucessão dos ciclos para entender o que acontece
IBO
239
Dependência de dados Quando há dependência de dados entre instruções,
algumas componentes precisam se manter ociosas durante alguns ciclos
Isto caracteriza desperdício de tempoa = b + cd = a + h
B E GRIBO
B E GRIBO
B
No primeiro ciclo, a instrução é buscada. Ou seja, ao seu final, a máquina apenas tem a instrução sem nem sequer saber o que significa, pois ainda não foi identificada
IBO
240
Dependência de dados Quando há dependência de dados entre instruções,
algumas componentes precisam se manter ociosas durante alguns ciclos
Isto caracteriza desperdício de tempoa = b + cd = a + h
B E GRIBO
B E GRIBO
IBO
B
Aqui, a máquina identifica a instrução “a = b + c” e já busca o valor dos operandos b e c. Neste mesmo ciclo, já é buscada a instrução “d = a + h”
IBO
241
Dependência de dados Quando há dependência de dados entre instruções,
algumas componentes precisam se manter ociosas durante alguns ciclos
Isto caracteriza desperdício de tempoa = b + cd = a + h
B E GRIBO
B E GRIBO
IBO E
IBO
Agora, aparece o problema. Neste ciclo, buscamos os operandos a e h da instrução 2, sendo que a é o resultado da instrução 1. E este resultado ainda não foi gravado, já que isto só terá sido feito ao final do ciclo seguinte
242
- -
Dependência de dados Quando há dependência de dados entre instruções,
algumas componentes precisam se manter ociosas durante alguns ciclos
Isto caracteriza desperdício de tempoa = b + cd = a + h
B E GRIBO
E GRIBO
Portanto, faz-se necessário esperar até que o valor de a esteja corretamente gravado em algum registrador
B
243
Note que, sem esse problema, levaríamos 5 ciclos para executar as 2 instruções. Com o impasse, levamos 2 ciclos a mais
Se a dependência de dados ocorresse seguidamente em 500 instruções, levaríamos 2 ciclos a mais para cada dependência
De pouco mais de 500 ciclos, a sequência passaria a levar cerca de 1500 ciclos para ser executada
Há ainda outro problema, muito mais comum do que a dependência de dados
244
Dependência de controle Sabemos que o endereço da instrução a ser buscada
na memória se encontra no registrador PC Sabemos também que, se não há desvios, a próxima
instrução a ser buscada está no endereço dado por pc (conteúdo de PC) + 1
No pipeline, busca-se uma instrução em um estágio, e já no próximo busca-se a instrução seguinte, no endereço pc + 1
245
Memória de Controle
MPCINC
MUX
AMUX
COND
N1
N0
LMS
ULANZ
Controle
Controle
Convém uma pequena revisão. Lembra-se deste esquema? É sobre como são feitos os desvios de instruções
ULA . . . . .
ADDR
246
Na verdade, não é necessário relembrar o que acontece em cada parte no desenho
Tudo o que precisamos ter em mente é que os desvios condicionais são realizados após alguma operação na ULA, a qual libera os sinais N e Z que contêm informações sobre a saída da ULA. Esta saída é, então, avaliada de acordo com a condição do desvio
Dito isso, conclui-se então que só no terceiro estágio saberemos se ocorrerá desvio
B E GRIBO
247
Isso significa que teremos problemas no pipeline se tivermos uma instrução parecida com:
Suponha que a instrução acima esteja no endereço 1 da memória principal
se a = 0 então goto 8
B E GRIBO
B E GRIBO
IBO
248
Isso significa que teremos problemas no pipeline se tivermos uma instrução parecida com:
Suponha que a instrução acima esteja no endereço 1 da memória principal
se a = 0 então goto 8
B E GRIBO
B E GRIBO
BI
BO
Neste ciclo, estaremos buscando a nossa instrução. PC contém seu endereço, então o conteúdo de PC é 1
249
Isso significa que teremos problemas no pipeline se tivermos uma instrução parecida com:
Suponha que a instrução acima esteja no endereço 1 da memória principal
se a = 0 então goto 8
B E GRIBO
B E GRIBO
IBO
B IBO
Agora, identificamos nossa instrução e buscamos o operando a. Vamos supor que este seja igual a 0. Neste mesmo ciclo, buscamos a próxima instrução, por default, no endereço pc + 1. Está sendo buscada, então, a instrução no endereço 2
250
Isso significa que teremos problemas no pipeline se tivermos uma instrução parecida com:
Suponha que a instrução acima esteja no endereço 1 da memória principal
se a = 0 então goto 8
B E GRIBO
B E GRIBO
I EI
BO
Porém, chegamos agora ao ciclo onde a primeira instrução é executada. A ULA dará como saída o próprio operando a, que será avaliado na lógica de microssequenciamento e, como é igual a 0, a execução do programa teria de ser desviada para a instrução no endereço 8 (goto 8)
251
- -
Isso significa que teremos problemas no pipeline se tivermos uma instrução parecida com:
Suponha que a instrução acima esteja no endereço 1 da memória principal
se a = 0 então goto 8
B E GRIBO
B E GRIBO
E agora? Constatamos anteriormente que a instrução no endereço 2 já foi buscada, e a máquina a essa altura já identificou e buscou os operandos dessa instrução que não nos interessa
252
- -
Isso significa que teremos problemas no pipeline se tivermos uma instrução parecida com:
Suponha que a instrução acima esteja no endereço 1 da memória principal
se a = 0 então goto 8
B E GRIBO
B E GRIBO
Mais uma vez, é necessário esperar alguns ciclos até que tenhamos a confirmação de que haverá desvio, para só depois buscar a próxima instrução
253
Nós, como projetistas da máquina, precisamos sempre buscar formas de passar por todos os obstáculos existentes em uma implementação
A saída geralmente é pensar em otimizações que diminuam o prejuízo causado por possíveis problemas de uma implementação
254
A primeira otimização, que qualquer bom compilador deve ser capaz de fazer, é uma arrumação na sequência de instruções a fim de diminuir ao máximo o número de ocorrências de dependência de dados em um programa
Vimos que a sequênciaa = b + cd = a + h
causaria um desperdício de 2 ciclos na máquina Entretanto, o compilador pode “procurar” outras
instruções que nada tenham a ver com estas e executá-las neste espaço
255
Por exemplo, se houver no programa outras duas instruções de soma, a ordem de execução pode ser:
a = b + cx = y + zm = n + pd = a + h
Desta forma, não haverá perda de tempo
B E GRIBO
B E GRIBO
B E GRIBO
B E GRIBO
a = b + c
x = y + z
m = n + p
d = a + h
O processador acaba de gravar o resultado em algum registrador
Inicia-se a busca dos operandos. Quando o registrador onde está a for consultado, lá já estará o valor correto de a
256
Outra otimização possível é conhecida como Data Forwarding
Consiste em buscar os operandos sem esperar que o resultado da instrução anterior seja gravado em um registrador, mesmo que um desses operandos seja esse resultado
ULA
Registradores
257
Na sequência:a = b + cd = a + h
sabemos que a segunda instrução também pode ser escrita como “d = b + c + h”
Desta forma, não precisamos esperar até que o registrador da variável a seja atualizado para buscar os operandos da segunda instrução
Em outras palavras, não é necessário esperar chegarmos ao final do estágio onde o resultado é gravado (GR) para conhecermos um dos operandos da segunda instrução
258
- -
B E GRIBO
E GRIBOB
Antes, precisávamos do valor gravado em um registrador
259
- -
B E GRIBO
E GRIBOB
Agora, podemos reaproveitar este valor logo após o término da execução da operação na ULA
Esta seria uma opção bastante útil para um caso do tipo:
a = b + cd = a + ef = d + gh = f + ij = h + kx = j + h
260
Processadores como o da Intel possuem uma série de outras otimizações na implementação do pipeline
Prejuízos causados por dependência de controle são diminuídos com maneiras de “prever” se a instrução atual causará desvio na execução da sequência de instruções, com probabilidades altas de acerto
Outra otimização possível é armazenar certas informações quando houver desvios na instrução, para serem usadas quando esta instrução aparecer novamente. Por exemplo, em um loop onde são feitas 10 iterações, ocorrem 9 desvios para a mesma instrução (a primeira)
261
Acompanhe:
i <- 1;
para i = 1 até 10 faça
instrução 1;instrução 2;instrução 3;i <- i + 1;
fim do loop
i = 1
262
Acompanhe:
i <- 1;
para i = 1 até 10 faça
instrução 1;instrução 2;instrução 3;i <- i + 1;
fim do loop
A condição é analisada e não ocorre desvio: entramos no loop
263
Acompanhe:
i <- 1;
para i = 1 até 10 faça
instrução 1;instrução 2;instrução 3;i <- i + 1;
fim do loopHora de analisar novamente se i é menor ou igual a 10
264
Acompanhe:
i <- 1;
para i = 1 até 10 faça
instrução 1;instrução 2;instrução 3;i <- i + 1;
fim do loopNeste momento, i = 2, então para iniciar a segunda iteração...
265
Acompanhe:
i <- 1;
para i = 1 até 10 faça
instrução 1;instrução 2;instrução 3;i <- i + 1;
fim do loop
...desviamos de volta para a primeira instrução no loop
266
Há ainda uma última forma de otimização que melhora o desempenho da nossa máquina, conhecida como superpipelining
Vimos que a implementação do pipeline agiliza, idealmente, ne vezes a execução das instruções em relação à nossa máquina antiga
Ora, por que então usar apenas 4 estágios? Um dos processadores da Intel possui pipeline de
20 estágios. Apenas para dar uma ideia, os primeiros 4 estágios são usados para operações de busca de instruções
. . .
. . .
B1 B2 B3 B4
267
Esta configuração caracteriza exatamente um superpipeline
Ok, 20 estágios já está melhor que 4. Mas por que não usar, talvez, 1000 estágios?
Para executar várias operações do mesmo tipo ao mesmo tempo, precisaríamos aumentar o número de componentes. No caso de 4 operações de busca, por exemplo, o processador precisa ter 4 PCs para armazenar o endereço dessas instruções, 4 IRs para armazenar as instruções propriamente ditas, etc
Aumentar o número de componentes encarece a máquina, o que tornaria um desastre elevar demais o número de estágios do pipeline
268
Muito bem, falamos bastante sobre como amenizar os revés do pipeline, mas não podemos esquecer que estes não são os únicos que afetam o desempenho da máquina
Até agora, economizamos alguns ciclos aqui, outros lá, já executamos várias instruções ao mesmo tempo e por aí vai
Mas não nos aprofundamos ainda em um outro problema que aumenta consideravelmente o tempo gasto pela máquina na execução de problemas: leitura e escrita na memória principal
Estas operações levam aproximadamente 100 ciclos para serem realizadas!
269
Memória Principal x Cache Uma das soluções possíveis para diminuir a
perda de tempo envolvida em operações de leitura e escrita na memória principal são as memórias cache
Este é um tipo de memória rápida, isto é, acessos a ela levam muito menos tempo, porém com a desvantagem de serem surrealmente caras
Recordando, de forma simplificada, o modelo de Von Neumann:
Memória Processador
270
Temos as seguintes opções: A que usávamos antes, memória principal, lenta, mas
com um tamanho maior sem um custo alto demais
Uma memória cache, caríssima e, portanto, de tamanho bem limitado, mas muito mais rápida
Memória Principal
Processador
Cache Processador
271
Podemos sonhar quesomos infinitamentericos e simplesmenteaumentar a cache até otamanho que queremos
Ou podemos voltar à realidade...
272
Agora, vem a pergunta “manjada”: será que é possível ter o benefício da rapidez da memória cache sem ter que aumentá-la a ponto de deixar a máquina cara demais, e ao mesmo tempo ter o tamanho de uma memória principal razoável?
Podemos começar com uma constatação básica: utilizando uma memória convencional, gastaremos 100 ciclos (tempo de leitura na memória, como já vimos) pelo menos uma vez por instrução, pois precisamos buscar cada uma delas na memória
Então, já seria um bom começo não precisar buscar uma mesma instrução duas vezes
273
Podemos arrumar nossa máquina da seguinte forma:
Já que a cache tem um tamanho bastante limitado, podemos usá-la pelo menos para armazenar instruções que já tenham sido buscadas. Assim, quando o processador precisar de alguma instrução pela segunda vez, ela será carregada da cache, o que levará muito menos tempo do que carregá-la da memória principal novamente
Memória Principal
ProcessadorCache
endereçosendereços
dados dados
274
Vamos agora supor uma instrução que é executada k vezes ao longo de um programa
Na arquitetura em que só havia processador e memória principal, o tempo médio para buscar essa instrução seria, naturalmente, o tempo gasto em uma operação de busca na memória
Já na arquitetura proposta no slide anterior, sabemos que a instrução só será buscada na memória principal na primeira vez. Nas outras (k - 1) vezes, ela será carregada da cache. Logo, o tempo médio para buscar essa instrução será:
275
Para k muito grande, temos k >> tmem, e k ≈ k - 1. Daí:
Ou seja, o tempo médio de busca dessa instrução será o tempo de buscá-la na cache, que era o que queríamos antes
E nem precisamos gastar uma senhora grana com uma cache do tamanho da memória principal; bastou usar uma menor com alguma inteligência
276
Existe um tipo especial de cache, chamado cache associativa, que recebe da memória principal um conjunto de instruções, em vez de apenas uma por vez. Estes conjuntos são os blocos
Blocos de instruções são divisões feitas tanto na memória principal quanto na cache, de tamanho fixo e pré-determinado
Por exemplo, podemos dividir a memória principal em blocos de tamanho 4 (cada um contém 4 palavras):
.
.
.
bloco 0
bloco 1
bloco 2
bloco 3
bloco 4
277
Sempre que o processador pedir uma instrução, a cache armazenará todo o bloco de onde essa instrução faz parte:
Vamos dar um zoom em parte da memória do slide anterior. Suponha que o processador precisa da palavra no endereço 4:
0123456789
1011
278
Sempre que o processador pedir uma instrução, a cache armazenará todo o bloco de onde essa instrução faz parte:
Vamos dar um zoom em parte da memória do slide anterior. Suponha que o processador precisa da palavra no endereço 4:
0123456789
1011
Cache
As palavras nos endereços 5, 6 e 7 também são carregadas para a cache
279
Se as palavras nos endereços 4, 5, 6 e 7 forem todas instruções e estas forem executadas sequencialmente, apenas uma consulta na memória principal será necessária, em vez de 4
Considerando que, em aplicações reais, o tamanho dos blocos tende a ser muito maior que 4, pode-se dizer que conseguimos uma grande vantagem
Analisaremos mais a fundo a cache associativa para entender melhor suas vantagens
280
Cache associativa para blocos de tamanho 4:
1 3 P0 P1 P2 P3
1 5 P0 P1 P2 P3
0 2 P0 P1 P2 P3
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
validade(0 ou 1)
nº dobloco
conteúdo do bloco(Pn é a n-ésima palavra do bloco)
entradas(ou linhas)
281
Validade é o bit que diz se aquele bloco está sendo usado ou é lixo. 1 significa que o bloco está sendo usado, enquanto 0 quer dizer que aquela linha pode ser sobrescrita
Número do bloco é o número (evidentemente binário) que representa onde aquele bloco estava na memória principal. Se o bloco armazena as palavras vindas dos endereços 0, 1, 2 e 3 da memória, então seu número é 0; se armazena as palavras dos endereços 4, 5, 6 e 7, então seu número é 1; etc
Quando o processador pede alguma palavra da memória, todas as linhas de validade 1 são verificadas, e a palavra é buscada na memória principal apenas se não existir na cache
Mas como essa checagem é feita?
282
Antes, precisamos entenderuma certa “mágica”
Seja a memória dividida emblocos de tamanho n
Cortando os log₂n bits menos significativos do endereço de uma palavra, obtemos exatamente o bloco onde ela está
Por exemplo, vimos que, dividindo em blocos de tamanho 4, a instrução no endereço 7 está no bloco 1
7 na base 2, em 8 bits, é igual a: 00000111
283
Antes, precisamos entenderuma certa “mágica”
Seja a memória dividida emblocos de tamanho n
Cortando os log₂n bits menos significativos do endereço de uma palavra, obtemos exatamente o bloco onde ela está
Por exemplo, vimos que, dividindo em blocos de tamanho 4, a instrução no endereço 7 está no bloco 1
7 na base 2, em 8 bits, é igual a: 00000111log₂4 = 2; então, tirando os 2 bits menos significativos, obtemos o valor 1, que é o número do bloco da instrução 7
284
Na verdade, não é algo tão difícil de aceitar se pensarmos em uma generalização
Vamos pensar na base 10: considere uma memória dividida em blocos de tamanho 100
Os endereços 0 até 99 estão no bloco 0, do 100 até 199, estão no bloco 1, e por aí vai
Para obter o bloco da palavra no endereço 374, precisamos tirar os log₁₀100 bits menos significativos (mudamos para a base 10, então a base do logaritmo passa a ser 10)
374
285
Na verdade, não é algo tão difícil de aceitar se pensarmos em uma generalização
Vamos pensar na base 10: considere uma memória dividida em blocos de tamanho 100
Os endereços 0 até 99 estão no bloco 0, do 100 até 199, estão no bloco 1, e por aí vai
Para obter o bloco da palavra no endereço 374, precisamos tirar os log₁₀100 bits menos significativos (mudamos para a base 10, então a base do logaritmo passa a ser 10)
3log₁₀100 = 2, então tiramos os 2 algarismos menos significativos. A palavra está no bloco 3
3
286
Voltando ao que interessa, vamos supor que o valor 00001001 chegue ao MAR no processador. Isto significa que o processador está pedindo a palavra que está no endereço 9 (bloco 2)
Os 2 bits menos significativos são ignorados, e o valor 000010 é comparado, na cache, a todos os números de bloco pertencentes a linhas de validade 1
Mas como uma consulta na cache pode ser tão rápida, se ainda precisamos fazer uma busca sequencial para saber se o bloco 2 já está de fato armazenado na cache?
287
Na verdade, a busca não é sequencial Em cada uma das linhas da cache, existem portas
lógicas que comparam simultaneamente o número do bloco nela presente ao valor recebido
1 3 P0 P1 P2 P3
1 5 P0 P1 P2 P3
1 2 P0 P1 P2 P3
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
000010
portas
lógicas
3
5
2
os valores são diferentes
os valores são iguais
288
Esse festival de portas lógicas constitui um importante motivo do encarecimento da cache
Isso porque, como vimos desde os primeiros slides, hardware tem uma grande influência no preço final da máquina
289
O bloco 2 então está na cache. Mas como encontrar a palavra certa (a do endereço 9) em meio às 4 do bloco, que contém as palavras dos endereços 8, 9, 10 e 11?
Recordando: a palavra pedida é a do endereço 00001001, e para obter o bloco ignoramos os 2 bits mais à esquerda, 01
E estes dois bits são exatamente os bits que informam a palavra certa a ser buscada no bloco!
Então, a palavra do endereço 9 é a P₁ do bloco 2Dessa forma, carregamos a palavra direto da
cache, sem consulta à memória principal
290
Existe outro tipo de cache, mais barato que a associativa, a cache com mapeamento direto
Nela, cada linha da cache é destinado a blocos específicos da memória
Dessa forma, não é necessário ter circuitos comparativos em cada linha da cache, já que a procura será feita apenas em uma linha específica e não mais em todas elas
Com isso, conseguimos baratear a cache significativamente
Veremos que o esquema é bem parecido com o da cache associativa:
291
Cache com mapeamento direto para blocos de tamanho 4:
1 000 P0 P1 P2 P3
1 001 P0 P1 P2 P3
0 101 P0 P1 P2 P3
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
validade(0 ou 1) tag
conteúdo do bloco(Pn é a n-ésima palavra do bloco)
entradas(ou linhas)
292
Antes, armazenávamos blocos vindos da memória enquanto houvesse linhas disponíveis
Agora, as linhas destinam-se a grupos fixos de blocos. Porém, sabemos que cada linha só pode armazenar um bloco
Então, se uma linha é destinada aos blocos X e Y, apenas um deles pode estar na cache ao mesmo tempo. Se na cache já existir o bloco X, e o bloco Y for carregado da memória, este bloco irá para a linha onde já está o bloco X, sobrescrevendo-a
Vamos definir que na nossa cache, cada linha armazena 4 possíveis blocos
293
A arrumação mais óbvia seria:
Porém, perceba que, desta forma, dos blocos 0, 1, 2 e 3, nunca poderíamos ter mais de um na cache ao mesmo tempo. O mesmo vale para os outros quartetos de blocos
Isto significa que, se as palavras nesses blocos fossem buscadas sequencialmente, perderíamos sempre o bloco anterior
0
1
2
validade tag conteúdo do bloco
.
.
.
0, 1, 2, 3
4, 5, 6, 7
8, 9, 10, 11
294
Vamos focar na linha 0 para entender melhor:
0
bloco 0
bloco 1
bloco 2
bloco 3
palavras nos endereços 0 a 15,
consultadas sequencialmente
295
Vamos focar na linha 0 para entender melhor:
0
bloco 0
bloco 1
bloco 2
bloco 3
Ao buscar a palavra no endereço 0, o bloco 0 é carregado para a linha 0 da cache
0
296
Vamos focar na linha 0 para entender melhor:
0
bloco 0
bloco 1
bloco 2
bloco 3
Na busca da palavra no endereço 4, o bloco 1 é carregado para a linha 0 da cache. A cache perde, assim, o bloco 0
4
297
Vamos focar na linha 0 para entender melhor:
0
bloco 0
bloco 1
bloco 2
bloco 3
O mesmo acontece para as buscas das palavras nos endereços 8...
8
298
Vamos focar na linha 0 para entender melhor:
0
bloco 0
bloco 1
bloco 2
bloco 3
...e 12!
12
299
Note que, após todas essas buscas, teríamos os 4 blocos armazenados na cache associativa
Na cache por mapeamento direto, porém, ficamos apenas com o último bloco da sequência
Nesse caso, se as palavras desses 4 blocos fossem instruções internas a um loop, teríamos que consultar a memória a cada mudança de bloco. Já na cache associativa, nenhum acesso à memória teria de ser feito depois que os 4 blocos já estivessem na cache
Mas nem tudo está perdido. Basta fazermos com que cada linha destine-se a blocos distantes uns dos outros:
300
Com isso, o problema do loop que acabamos de ver será mais difícil de acontecer
Agora, quando carregarmos o bloco 0 na cache, ele só será sobrescrito quando os blocos 8, 16 ou 24 forem carregados da memória
Supondo uma execução sem excessivos desvios, isto é, um programa com razoável localidade, é perfeitamente possível que não precisemos mais do bloco 0 quando carregarmos um dos outros 3
0
1
2
.
.
.
0, 8, 16, 24
1, 9, 17, 25
2, 10, 18, 26
301
Outro ganho que temos com este arranjo é a diminuição da largura da cache, substituindo a antiga coluna que guardava o nº do bloco por uma coluna tag
A linha 0 destina-se aos blocos 0 (00000), 8 (01000), 16 (10000) e 24 (11000). A linha 1 destina-se aos blocos 1 (00001), 9 (01001), 17 (10001) e 25 (11001), e assim por diante
Repare na semelhança: para cada linha, os 3 últimos bits dos seus possíveis blocos são iguais
Isto significa que é possível identificar qual bloco aquela linha está armazenando apenas pelos bits que diferem entre os blocos
302
Assim, se o valor da tag na linha 0 for 000, sabemos que nela está o bloco 0; se for 010, sabemos que lá está o bloco 8; já o valor 100 indica que ela guarda o bloco 16; e 110 indica o bloco 24
Na linha 1, estes mesmos valores acusam, respectivamente, os blocos 1, 9, 17 e 25
Resumindo: quando o processador precisa de uma palavra que já está na cache, a consulta é imediatamente direcionada para a linha onde o bloco daquela palavra deveria estar. Por exemplo, se o processador pede uma palavra do bloco 8, a consulta é feita diretamente na linha 0
Porém, se lá estiver o bloco 16, o bloco 8 é carregado da memória e salvo na linha 0, sobrescrevendo o 16
303
Supondo uma memória principal com 32 blocos, teríamos a cache disposta dessa forma:
Repare que a altura da cache precisa sempre ser uma potência de 2, para que possamos identificar o bloco armazenado através da tag
0 0, 8, 16, 24
1 1, 9, 17, 25
2 2, 10, 18, 26
3 3, 11, 19, 27
4 4, 12, 20, 28
5 5, 13, 21, 29
6 6, 14, 22, 30
7 7, 15, 23, 31
304
Se tirássemos a última linha da cache, por exemplo, não teria como redistribuir os blocos 7, 15, 23 e 31 sem que todos os blocos destinados a uma mesma linha possuíssem bits em comum. Logo, seria impossível identificá-los através da tag
Em alguns casos, esta restrição de tamanho pode ser bastante inconveniente: suponha que temos dinheiro para construir até 1023 linhas de uma cache por mapeamento direto
A potência de 2 mais próxima é 1024 (2¹⁰), mas não conseguimos atingir esse número. Logo, nossa cache será forçada a ter apenas 512 linhas
Já a cache associativa poderia sem problemas ter tantas linhas quanto pudéssemos pagar
305
Uma forma de unir os benefícios desses dois tipos de cache é literalmente unir as duas caches
Se “grudarmos” várias caches com mapeamento direto, lado a lado, teremos na horizontal uma espécie de cache associativa
.
.
.
.
.
.
.
.
.
.
.
.
V tag cont. do bloco
.
.
.
.
.
.
.
.
.
.
.
.
V tag cont. do bloco
.
.
.
.
.
.
.
.
.
.
.
.
V tag cont. do bloco
. . .
306
Isso consiste em um terceiro tipo de cache, chamado cache associativa por conjunto
A altura continua sendo necessariamente uma potência de 2, mas a largura é arbitrária
Portanto, se temos dinheiro para 1536 linhas de cache, basta juntar 3 caches com mapeamento direto de 512 linhas cada
Com isso, não precisamos sobrescrever uma linha sempre que um novo bloco destinado a ela for carregado da memória; basta passá-lo para o lado
307
Já que temos mais de um bloco por linha, precisamos de portas lógicas para identificar em qual deles está a palavra pedida pelo processador, exatamente como fazíamos na cache associativa
Só que desta vez, o número de portas lógicas de que precisamos não equivale ao número de linhas da cache, e sim ao número de caches por mapeamento direto unidas lado a lado
Temos com isso uma significativaredução de preço, sem umasignificativa perda de eficiência
308
Projeto de Instruções
Com o que vimos até aqui, não foi possível ter uma noção do quanto o formato das instruções pode variar de máquina para máquina
Por exemplo, o formato das instruções lidas por um processador de celular é, em geral, totalmente diferente do formato das instruções entendidas por um processador de um PC
309
Imagine que um novo processador para PCs(personal computers) seja lançado, e é muito mais rápido e muito maisbarato que todos osoutros. Tentador, é claro
Porém, o novo processador possui uma linguagem diferente, isto é, entende instruções diferentes dos outros processadores
310
O resultado é que este processador será basicamente inútil, mesmo com tantas vantagens
É claro que é possível desenvolver um compilador que traduza programas os programas de níveis superiores para a linguagem deste novo processador, mas isto não é suficiente
Quando instalamos softwares em nosso computador, estamos lidando apenas com o executável gerado por algum programa
Isto quer dizer que esses softwares já foram compilados anteriormente, de acordo com a linguagem dos processadores antigos. Logo, nada rodará no PC que utilizar o novo processador
311
O tipo de aplicação é determinante na escolha de cada detalhe da máquina que será desenvolvida para aquela aplicação
Em aplicações onde o desempenho é fundamental, o projetista escolherá as opções que tornam o processamento mais ágil, sem se importar muito com o custo (ex.: sistemas da NASA, supercomputadores)
Já em outras, a prioridade é minimizar o custo. Para essas, o projetista escolherá as opções que barateiam a máquina (ex.: celulares)
312
Vamos entender agora que critérios o projetista deveria levar em conta, na questão das instruções e seus formatos, caso fosse desenvolver uma nova máquina
Primeiramente, instruções curtas são mais vantajosas que as longas, por ocuparem menos espaço na memória
h
w
h
w/2
h.w bits h.w/2 bits
313
Além disso, cada memóriapossui uma taxa de transfe-rência de bits por segundo
Sendo assim, quanto menosbits uma instrução tiver, mais instruções poderão ser executadas a cada segundo
Isto dá ainda mais importância à atenção que se deve ter com a largura das instruções
Deve-se ter, porém, a mesma atenção para não diminuir demais a largura da instrução a ponto de não ser mais possível representar todas as operações que a máquina pode realizar
314
Para uma máquina capaz de realizar 2ⁿ operações, é impossível que as instruções tenham menos de n bits de largura, já que dessa forma não seria possível ter um opcode diferente para cada operação
Mesmo que as instruções não tenham operandos, como representar 8 operações com instruções de 2 bits de largura?
315
Para uma máquina capaz de realizar 2ⁿ operações, é impossível que as instruções tenham menos de n bits de largura, já que dessa forma não seria possível ter um opcode diferente para cada operação
Mesmo que as instruções não tenham operandos, como representar 8 operações com instruções de 2 bits de largura?
operação 1 00
316
Para uma máquina capaz de realizar 2ⁿ operações, é impossível que as instruções tenham menos de n bits de largura, já que dessa forma não seria possível ter um opcode diferente para cada operação
Mesmo que as instruções não tenham operandos, como representar 8 operações com instruções de 2 bits de largura?
operação 1
operação 2
00
01
317
Para uma máquina capaz de realizar 2ⁿ operações, é impossível que as instruções tenham menos de n bits de largura, já que dessa forma não seria possível ter um opcode diferente para cada operação
Mesmo que as instruções não tenham operandos, como representar 8 operações com instruções de 2 bits de largura?
operação 1
operação 2
operação 3
00
01
10
318
Para uma máquina capaz de realizar 2ⁿ operações, é impossível que as instruções tenham menos de n bits de largura, já que dessa forma não seria possível ter um opcode diferente para cada operação
Mesmo que as instruções não tenham operandos, como representar 8 operações com instruções de 2 bits de largura?
operação 1
operação 2
operação 3
operação 4
00
01
10
11
319
Para uma máquina capaz de realizar 2ⁿ operações, é impossível que as instruções tenham menos de n bits de largura, já que dessa forma não seria possível ter um opcode diferente para cada operação
Mesmo que as instruções não tenham operandos, como representar 8 operações com instruções de 2 bits de largura?
operação 1
operação 2
operação 3
operação 4
operações 5 em diante
00
01
10
11
320
Instruções fazem parte de palavras na memória Ao falar de blocos e cache, falamos de palavras
de forma superficial, como se fossem o mesmo que instruções. Mas na verdade, uma palavra é formada por uma ou mais instruções na memória
Palavras possuem tamanho fixo, pois ficam armazenadas em células da memória e estas possuem tamanho único
Entretanto, é possível que o tamanho das instruções varie. Dessa forma, cada palavra pode armazenar um número diferente de instruções
321
Dito isso, podemos falar sobre outro aspecto importante a ser notado: é bastante conveniente associar o tamanho de uma palavra da máquina ao código do caracter
Um caracter é definido por uma sequência de bits, que constituem esse código
Na computação em geral, é frequente a necessidade de manipular sequências de caracteres
Se o tamanho de cada palavra for múltiplo do tamanho do código do caracter, asseguramos que não haverá desperdício de bits quando se tratar de uma sequência de caracteres
322
Imagine uma máquina em que cada caracter seja representado por 6 bits e a cada palavra tenha um comprimento de 28 bits
A palavra “inconstitucional” ficaria armazenada na memória da seguinte forma (simplificada):
Repare que foram desperdiçados 4 bits de cada célula de memória, totalizando 16 bits perdidos
6 12 18 24 28
i n c o
n s t i
t u c i
o n a l
bitsinúteis
bitsinúteis
bitsinúteis
bitsinúteis
323
Se fizermos o tamanho de cada palavra ser igual a 24 (múltiplo de 6), os caracteres se encaixariam perfeitamente e não haveria mais bits inúteis
Poderíamos também guardar partes do código de um caracter em palavras diferentes, mas isso aumentaria desnecessariamente o número de acessos
Por último, o tamanho de cada instrução depende também da forma como a memória é endereçada. Este aspecto está diretamente relacionado ao comprimento de cada palavra
324
Supondo uma máquina cujo código do caracter seja composto por 8 bits, e uma memória que precise armazenar 2¹⁶ caracteres
É possível fazer com que cada palavra tenha 8 bits, e desta forma cada célula de memória armazenaria 1 caracter. Nesse caso, seriam necessários 16 bits para indicar um endereço dessa memória
Agora suponha que uma instrução dessa máquina possui 8 bits reservados para opcode
Queremos comparar os caracteres que estão nos endereços 0 e 1 da memória, e o opcode da operação de comparação é 11111111
325
Com isso, nossa instrução ficaria assim:
Apenas essa instrução ocuparia 50 bits, isto é, 5 palavras!
Em algumas máquinas, o tamanho das instruções é variável ao longo da memória
É importante ressaltar que, da mesma forma que uma palavra pode conter várias instruções, uma instrução pode ocupar várias palavras caso o tamanho das instruções varie na memória
1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
opcode(8 bits)
endereço do 1º caracter(16 bits)
endereço do 2º caracter(16 bits)
326
Então, outra opção é organizar a memória em palavras mais longas. Se utilizássemos, por exemplo, palavras de 32 bits ao invés de 8, reduziríamos os bits de endereço de 2¹⁶ para 2¹⁴ (igual a (2¹⁶)/4, já que 32 = 8 x 4)
O revés disso seria a necessidade de instruções adicionais para extrair o caracter desejado de uma palavra, que agora não contém aquele caracter apenas
char char char char char
char
char
char char char char
char char char char
0
1
2
0
1
2
caracter é localizável apenas pelo endereço
necessárias mais instruções para localizar um caracter entre os 4 armazenados por uma palavra
327
Vamos considerar agora apenas máquinas nas quais o tamanho das instruções não varia
Uma instrução com n bits de opcode permite à máquina 2ⁿ operações diferentes, enquanto k bits de endereço permitem endereçar 2k células de memória
Caso queiramos aumentar o número de células da memória, somos obrigados a reduzir o número de operações da máquina
A menor mudança que podemos fazer é acrescentar um bit de endereço, retirando um de opcode. Porém, só isso já acrescenta o dobro de endereços possíveis de serem indicados, em troca da perda de metade das possíveis operações
328
E se uma máquina tivesse 65 (2⁶ + 1) operações diferentes e uma memória com 512 (2⁹) células?
A máquina teria que ter no mínimo 7 bits de opcode e 9 bits de endereço, totalizando 16 bits; caso contrário, não seria possível representar todas as operações ou todos os endereços
Perceba que, por causa de somente uma operação, a princípio não é possível projetar a máquina com instruções de tamanho 15
Memórias armazenam milhares, milhões ou até bilhões de instruções. Logo, um bit a mais ou a menos por instrução faz grande diferença
329
Através da técnica conhecida como expansão de código de operação, é possível representar as 65 operações em 15 bits, sem sacrificar os 9 bits reservados ao endereçamento
A técnica consiste em fazer com que “um opcode represente várias operações em vez de uma”. A definição aparece entre aspas porque, na verdade, não se trata mais de um opcode, e sim de vários, originados pela expansão de um
Em outras palavras, reserva-se um opcode específico (por exemplo, 1111). A instrução que tiver esses 4 bits iniciais terá então, 8 bits de opcode e não mais 4
330
Dessa forma, instruções começadas em 11110000 e 11110001 por exemplo, passam a representar 2 operações
Naturalmente, esses 2 “novos” opcodes não podem representar operações que envolvem endereços da memória, já que não há mais espaço para indicá-los (restaram apenas 7 bits)
Um exemplo prático e simples pode ser visto na primeira máquina que estudamos, na tabela de macroinstruções retirada do livro Organização Estruturada de Computadores:
331
332
Repare que as instruções que não começam por 1111 possuem 4 bits de opcode. Já as demais possuem 8, e nenhuma delas tem endereços da memória como operando
Existem diversas classificações que podemos fazer com as instruções, e uma delas é relativa ao modo de endereçamento
Mas precisamos passar para um conceito mais geral de “endereço”. Não mais se tratará da memória principal apenas, mas de qualquer local onde instruções e/ou dados podem ser armazenados. Nesta definição, incluem-se os registradores da CPU
333
A classificação é bastante simples e é feita de acordo com a forma como cada instrução se refere ao(s) seu(s) operando(s)
Suponhamos uma instrução que faz o acumulador (registrador especial da CPU, já visto anteriormente) receber o seu próprio valor somado a 5, isto é, ac ← ac + 5
No endereçamento imediato, a instrução explicita claramente que é o valor 5 a ser somado com o valor do acumulador
No endereçamento direto, não é 5 o valor fornecido na instrução, mas sim o endereço da memória onde aquele valor está
334
Um método de endereçamento bem parecido com o anterior é o endereçamento de registrador, no qual a instrução fornece o endereço do registrador (e não da memória) onde está o valor a ser somado
No endereçamento indireto, a instrução fornece um endereço da memória onde se encontra um apontador, isto é, um número que representa o endereço onde de fato está o valor a ser somado. No nosso exemplo, se o valor 5 está no endereço 10 da memória, e o número 10 está no endereço 1, o acumulador será carregado com o valor 1. Então o apontador 10 presente no endereço 1 indicará a célula onde está o valor 5, que será somado como manda a instrução
335
O método de indexação auxilia em operações sobre estruturas de dados armazenados sequencialmente na memória Utiliza-se um ou mais registradores, os registradores
de índice, os quais possuem endereços de memória a serem acessados
Se queremos por exemplo, copiar o conteúdo de um vetor que começa na posição X para outro na posição Y, uma instrução pode indicar os endereços X e Y da memória e inicializar o registrador de índice com o valor 0. Desta forma, o valor contido em X + 0 será copiado para Y + 0
Depois, incrementa-se o registrador de índice até a última posição dos vetores. Assim, o valor em X + 1 será copiado para Y + 1, o valor em X + 2 será copiado para Y + 2, e assim por diante
336
Generalizando, pode-se dizer que o valor na posição X + n é copiado para o valor Y + n, onde n é o valor do registrador de índice e vai de 0 a k-1, sendo k o tamanho dos vetores
Esquematizando:
.
.
.
.
.
.
início do vetor 1(endereço X)
fim do vetor 1(endereço X + k - 1)
.
.
.
registradorde índice
.
.
.
vetor 1
.
.
.
vetor 2
v1
v2
vk
início do vetor 2(endereço Y)
fim do vetor 2(endereço Y + k - 1)
337
Generalizando, pode-se dizer que o valor na posição X + n é copiado para o valor Y + n, onde n é o valor do registrador de índice e vai de 0 a k-1, sendo k o tamanho dos vetores
Esquematizando:
.
.
.
.
.
.
início do vetor 1(endereço X)
fim do vetor 1(endereço X + k - 1)
.
.
.
registradorde índice
.
.
.
vetor 1
.
.
.
vetor 2
v1
v2
vk
0
RI é inicializado com 0início do vetor 2(endereço Y)
fim do vetor 2(endereço Y + k - 1)
o valor v1, que está em
X + 0, é copiado para
Y + 0
v1
338
Generalizando, pode-se dizer que o valor na posição X + n é copiado para o valor Y + n, onde n é o valor do registrador de índice e vai de 0 a k-1, sendo k o tamanho dos vetores
Esquematizando:
.
.
.
.
.
.
início do vetor 1(endereço X)
fim do vetor 1(endereço X + k - 1)
.
.
.
registradorde índice
.
.
.
vetor 1
.
.
.
vetor 2
v1
v2
vk
1
RI é incrementado e passa a ter o valor 1início do vetor 2
(endereço Y)
fim do vetor 2(endereço Y + k - 1)
o valor v2, que está em
X + 1, é copiado para
Y + 1
v1
v2
339
Generalizando, pode-se dizer que o valor na posição X + n é copiado para o valor Y + n, onde n é o valor do registrador de índice e vai de 0 a k-1, sendo k o tamanho dos vetores
Esquematizando:
.
.
.
.
.
.
início do vetor 1(endereço X)
fim do vetor 1(endereço X + k - 1)
.
.
.
registradorde índice
(RI)
.
.
.
vetor 1
.
.
.
vetor 2
v1
v2
vk
k - 1
RI é incrementado pela última vez e passa a ter o valor k - 1início do vetor 2
(endereço Y)
fim do vetor 2(endereço Y + k - 1)
o processo se repete até que vk, que está em X + k - 1, é
copiado para Y + k - 1
v1
v2
vk
340
Finalmente, no endereçamento de pilha, a instrução fornece apenas a operação a ser feita. Seu opcode já irá especificar que os operandos são os dois presentes no topo de uma pilha. Estes são retirados e a operação fornecida é executada sobre eles, e o resutado é colocado de volta na pilha
341
Finalmente, no endereçamento de pilha, a instrução fornece apenas a operação a ser feita. Seu opcode já irá especificar que os operandos são os dois presentes no topo de uma pilha. Estes são retirados e a operação fornecida é executada sobre eles, e o resutado é colocado de volta na pilha
3
5
.
.
.
.
.
stackpointer
342
Finalmente, no endereçamento de pilha, a instrução fornece apenas a operação a ser feita. Seu opcode já irá especificar que os operandos são os dois presentes no topo de uma pilha. Estes são retirados e a operação fornecida é executada sobre eles, e o resutado é colocado de volta na pilha
3
5
.
.
.
.
.
stackpointer
.
.
.
.
.
stackpointer
3 + 5 = 8
343
Finalmente, no endereçamento de pilha, a instrução fornece apenas a operação a ser feita. Seu opcode já irá especificar que os operandos são os dois presentes no topo de uma pilha. Estes são retirados e a operação fornecida é executada sobre eles, e o resutado é colocado de volta na pilha
3
5
.
.
.
.
.
stackpointer
.
.
.
.
.
stackpointer
3 + 5 = 88
.
.
.
.
.
stackpointer
344
Outra classificação possível para instruções é quanto ao seu tipo, o qual indica superficialmente sobre o que a instrução é responsável em termos de execução
Instruções de transferência de dados servem para copiar dados não só de um endereço para o outro na memória, mas também para registradores e pilhas, por exemplo
Instruções que utilizam operações diáticas são aquelas que combinam dois operandos com a finalidade de produzir um resultado. Exemplos comuns são instruções de soma, subtração, multiplicação, divisão e operações booleanas
345
Já as instruções com operações monádicas utilizam apenas um operando para produzir um resultado. É o caso da operação booleana de negação e do deslocamento de bits, entre outros
Instruções de comparação e desvios condicionais também são frequentemente utilizadas e sua classificação é auto explicativa: testam valores e operam desvios caso os valores atendam uma determinada condição
Chamadas de procedimentos são instruções que iniciam a execução de um grupo de instruções (por exemplo, funções) dentro de um programa. Como vimos há alguns (muitos) slides atrás, a chamada de uma função geralmente invoca a criação de uma pilha, tarefa que cabe às chamadas de procedimentos
346
Instruções de controle de loop não haviam sido vistas até agora, até por serem bem particulares, mas servem basicamente para controlar iterações de uma forma menos “manual” Tínhamos uma instrução para incrementar uma
variável que controlava o loop, e depois outra para testar esta variável e executar um desvio se preciso, sendo que ambas eram processada a cada iteração
Uma instrução de controle de loop, na única vez em que é processada, inicia um contador e especifica uma condição. O contador é testado a partir desta condição, e quando a condição é satisfeita, o loop é encerrado. Seja x o número de iterações, deixamos de processar 2x instruções para processar apenas uma
347
Instruções de Entrada/Saída determinam as operações de leitura e escrita Tais instruções contam com um DMA (Direct Access
Memory ou Acesso Direto à Memória), que controla o fluxo de dados entre a memória principal e softwares que estão sendo executados
O DMA possui 4 registradores especiais, detalhados no seguinte esquema:
Endereço
Contador
Dispositivo
Direção
armazena o endereço da memória a ser lido/escrito
indica quantas palavras devem ser transferidas
indica o número do dispositivo de E/S a ser usado
armazena 0 ou 1, para informar se a operação será
de leitura ou escrita
348
O DMA frequentemente precisa interromper as atividades do processador para controlar operações de leitura e escrita, já que dispostivos de E/S geralmente não toleram atrasos. Seria bem desagradável se, por exemplo, para todas as letras que digitássemos do teclado, houvesse um pequeno atraso em sua exibição na tela
Para máquinas que fazem muitas operações de E/S, é necessária uma espécie de miniprocessador adicional. São os canais de dados, que podem até receber e executar programas sem nenhuma ajuda da CPU
No momento em que a CPU ordena o início de uma operação de E/S, o canal de dados entra em ação
349
O canal passa então a interagir com a memória para buscar tudo o que é necessário para executar instruções vindas de algum dispositivo de E/S sem que a CPU precise “se preocupar” com isso
Ficam armazenados na memória: um endereço de buffer, que informa onde dados devem ser lidos ou escritos pelo canal; o endereço do programa de canal, onde começa o programa que contém as instruções a serem executadas pelo canal; um contador, responsável por indicar quantas palavras possui o programa de canal; e alguns bits de flag, que informam atividades como interrupção do canal após o processamento de uma determinada instrução
350
Fluxo de Controle Saindo um pouco da
visão de instruçõesseparadas, vamos passara olhar um programacomo um todo, incluindoa ordem e a maneiracomo são executadas
Fluxo de controle é justamente a ordem em que as instruções de um programa, armazenadas sequencialmente na memória, são executadas
351
A essa altura, já estamos cansados de saber que as instruções de um programa, mesmo armazenadas sequencialmente na memória, não são necessariamente executadas na mesma sequência, por causa dos desvios que podem ocorrer ao longo da execução
Excetuando os desvios, também já vimos e revimos que a execução da próxima instrução se dá através da incrementação do contador de programa (PC), o qual indica o endereço da instrução na memória
Assim, temos os seguintes gráficos:
352
O gráfico à esquerda representa os valores de PC ao longo do tempo em uma execução totalmente sequencial (ideal), enquanto o gráfico à direita exibe esses valores com a ocorrências de desvios
353
Assim como em um programa escrito em linguagem de alto nível, é melhor organizar as instruções em métodos/funções em vez de deixar a “main” com um amontoado de instruções diferentes, o um macroprograma é melhor estruturado em procedimentos
O fluxo de controle também é alterado com a chamada de um procedimento, mas com a vantagem de que a execução retorna à linha da chamada após o seu término
Quando falamos sobre pilhas pela primeira vez, vimos que a chamada de um procedimento adiciona na pilha o endereço da chamada, justamente para que esse retorno aconteça
354
Imagine os dois retângulos abaixo como sequências de instruções sendo executadas
O procedimento à esquerda é chamado do programa principal, e chama vários outros procedimentos em pontos distintos
355
Em alguns casos, são necessárias as chamadas co-rotinas, isto é, quando um procedimento A chama um procedimento B, e o mesmo após executar algumas instruções, volta a chamar o procedimento A. Este, por sua vez, retoma a execução a partir da linha onde o procedimento B havia sido invocado
356
Outro modificador do fluxo de controle de um programa são as traps (armadilhas) que param a execução do programa por motivos de força maior. Ocorrências de overflow e outros tipos de exceções são alguns dos motivos
Finalmente, existem as interrupções, que, assim como as traps, interrompem a execução de um programa. Porém, desta vez, trata-se de uma parada temporária, geralmente para dar vez a operações de E/S que, como vimos, tem prioridade sobre as demais
357
Nível de Montagem Descreveremos agora, ainda que de forma
bastante superficial, um dos níveis acima do nível em que estão os macroprogramas
Os macroprogramas estão diretamente relacionados aos programas escritos em linguagem de montagem (Assembly), já que cada instrução em linguagem de montagem produz uma macroinstrução
A vantagem de se programar em Assembly está então somente na maior facilidade que se tem em usar nomes e endereços simbólicos em vez de apenas “zeros” e “uns”
358
Um fator importante sobre a maioria dos programas é a distribuição irregular do tempo de execução em cada parte dos mesmos
É possível que 90% do tempo de execução de um programa seja decorrido apenas em 10% do seu total
Utilizando o conceito de Program Tuning (afinação do programa), separam-se então esses 10% do programa e tenta-se otimizá-lo
Sem dúvidas, uma opção muito mais viável do que procurar por possíveis otimizações no programa inteiro
359
Passando ao processo de montagem em si, esta é geralmente feita em dois passos principais
No primeiro passo, o montador lê o programa e constrói a tabela de símbolos
Você deve se lembrar de que programas em Assembly possuem diversos labels, que particularizam linhas e direcionam a execução para essas linhas
Se o montador lesse o programa apenas uma vez e localizasse uma linha: JUMP LABEL1, ele não saberia para onde direcionar a execução por não saber que linha é identificada por esse label
360
Por isso, é necessário que a tabela de símbolos seja criada. Esta tabela nada mais é do que uma associação entre os labels e o endereço da linha que eles referenciam
É criada também uma tabela de códigos, que armazena informações sobre os códigos simbólicos de operações, como por exemplo: ADD (soma) e JMP (jump, pular para uma linha indicada pelo label que vier a seguir)
Entre essas informações estão os operandos, o código da operação hexadecimal e o tamanho da instrução em bytes
361
Na segunda leitura do programa, ainda falando sobre o primeiro passo, cada procedimento do programa é traduzido pelo montador (ou tradutor, ou ainda, compilador)
Os códigos gerados, chamados módulos objetos são conectados por um linker (ligador), gerando o executável
362
A tradução dos procedimentos para os módulos objeto representa uma mudança de níveis. É como se fossem partes do macroprograma, os quais se tornam o macroprograma final através da junção pela qual o linker é responsável
O executável gerado é também chamado módulo absoluto de carga
O carregador é então responsável por carregar este módulo para a memória principal, e a partir daí inicia-se a execução do macroprograma tal qual estudamos até aqui
363
As duas imagensao lado representama sequência de mó-dulos antes da inter-venção do linker edepois, respectiva-mente da esquerdapara a direita