Você está na página 1de 226

1 – Computadores

1.1 Introdução
A velocidade da evolução dos sistemas computacionais é sem precedentes na
história de quaisquer outros sistemas inventados pelo homem, excetuando-
se, talvez, hoje, a evolução dos sistemas de sequenciamento genético. Se os
sistemas de transporte tivessem evoluído tão rápido quanto os sistemas
computacionais de 1940 (década da implementação do primeiro computador
com código armazenado) até hoje, por exemplo, seria possível viajar de
Manaus a Vitória em 15 segundos pelo custo de aproximadamente R$1,00.

Cada vez que os computadores ficam mais baratos e mais rápidos, novas
aplicações se tornam viáveis. No passado recente, aplicações que eram
completamente impraticáveis se tornaram comuns. Por exemplo: caixas
automáticos, computadores em automóveis, laptops, etc.

O avanço na área de hardware permitiu também fantásticos avanços na área


de software. Alguns programas de computador de hoje, tais como bibliotecas
eletrônicas, programas para o reconhecimento de fala, jogos e sistemas de
realidade virtual, seriam classificados como ficção científica há poucos anos
atrás.

1.2 Níveis de Abstração: do código fonte ao código de máquina


Para se comunicar com um computador você tem que utilizar sinais
elétricos. Os sinais mais fáceis para um computador entender são ligado e
desligado, ou 0 e 1. Símbolos escritos usando apenas 0s e 1s são ditos
binários. Uma “letra” de um símbolo representado em binário é conhecida
como binary digit ou bit.

Computadores obedecem cegamente aos nossos comandos; por essa razão,


comandos individuais dados ao computador são chamados de instruções.
Instruções são, então, conjuntos de bits que o computador entende, ou
números escritos em binário. Por exemplo, o conjunto de bits

0001110010100000

diz a um computador específico para somar dois números.

aula1.doc 1 de 4
Usar números binários para se comunicar com um computador é
extremamente tedioso. Por essa razão, programadores logo inventaram uma
notação mais próxima de como pensamos para fazer esta comunicação.

A esta notação foi dado o nome de linguagem assembly. Ao primeiro


programa de computador capaz de traduzir de assembly para linguagem de
máquina (números binários) foi dado o nome assembler e este nome é usado
até hoje para designar programas que fazem esta tarefa.

Um programa assembler traduz, por exemplo, de

AR A, B #A=A+ B

para

0001110010100000

Embora seja um grande avanço usar assembly no lugar de linguagem de


máquina, assembly ainda é muito tedioso de usar.

Contudo, assim como é possível criar um programa para traduzir de


assembly para linguagem de máquina, também é possível criar um programa
para traduzir de uma linguagem de mais alto nível para linguagem de
máquina.

Estes programas foram denominados compiladores.

aula1.doc 2 de 4
Linguagens de alto nível, tais como C, FORTRAN, Pascal, etc, oferecem
importantes benefícios por aumentarem a produtividade dos programadores
– uma linha de código em linguagem de alto nível pode, muitas vezes,
representar várias instruções em linguagem de máquina.

aula1.doc 3 de 4
Com a evolução das técnicas de programação, muitos profissionais
perceberam que a reutilização dos programas era muito mais eficiente que
sempre reescreve-los do zero.

Assim, diversas subrotinas de uso generalizado passaram a ser agrupadas em


bibliotecas de rotinas. As subrotinas que permitem a configuração do
hardware e a comunicação com os dispositivos de entrada e saída, em
particular, logo foram agrupadas em um grande conjunto conhecido hoje
como sistema operacional.

Sistemas operacionais são programas que gerenciam os recursos disponíveis


no computador para o benefício dos programas que rodam no computador.

Software pode ser categorizado pelo seu uso. Temos, então, system software
e aplicações, onde o primeiro conjunto compreende os programas de suporte
à execução e ao desenvolvimento de novos programas e o segundo, os
programas para o usuário não desenvolvedor.

aula1.doc 4 de 4
1.3 Hardware dos Computadores: mouse, display, CPU, discos,
networks.
1.3.1 Mouse
O primeiro computador a usar um mouse foi o Alto, da Xerox, em 1973.

Em um mouse mecânico, uma esfera (1, na figura abaixo) toca a mesa e, ao


mesmo tempo, dois pequenos cilindros perpendiculares (2). Estes cilindros
então ligados a discos perfurados (3) e estes acoplados a um conjunto LED-
Sensor ótico (4 e 5).

Quando o mouse se move sobre a mesa, a esfera transmite o movimento aos


cilindros: um captura os movimentos para frente e para traz (4) e o outro,
para os lados (5).

Os discos ligados aos cilindros interrompem a luz do LED produzindo


pulsos no sensor que são tratados e enviados ao computador. Estes pulsos
indicam o quanto o mouse se moveu em cada direção.

O mouse e a interface gráfica do Alto foram fundamentais para o sucesso da


Apple: http://www.mac-history.net/computer-history/2012-03-22/apple-and-
xerox-parc

aula2.doc 1 de 10
Em um mouse óptico, um CI semelhante a uma câmera “fotografa” o mouse
pad ou superfície muitas vezes por segundo e mede os deslocamentos da
imagem.

1.3.2 Monitor
O tubo de raios catódicos (cathode ray tube – CRT) dos monitores, produz
imagens desenhando-as linha a linha. O CRT é, na verdade, uma válvula
eletrônica.

aula2.doc 2 de 10
Diodo termiônico

Triodo termiônico

aula2.doc 3 de 10
Em um CRT, um filamento aquece um catodo polarizado negativamente
com relação a outros elementos dentro do CRT.

Elétrons se libertam da superfície metálica do catodo devido ao calor e são


atraídos em direção à tela. Diversos elementos dentro do CRT, conhecidos
como grades, aceleram e focalizam os elétrons liberados pelo catodo,
produzindo um raio eletrônico que atinge a tela e lá é absorvido.

A tela é recoberta internamente por um composto a base de Fósforo que, ao


ser atingido pelo raio eletrônico, emite luz.

Bobinas magnéticas posicionadas no pescoço do CRT desviam o raio


eletrônico para cima, para baixo e para os lados, ao mesmo tempo em que
uma das grades regula a intensidade do feixe de elétrons. Desta forma, o
feixe de elétrons desenha as imagens na tela.

aula2.doc 4 de 10
Um CRT colorido possui três canhões que são focados precisamente em
pontos específicos da tela recobertos por compostos à base de fósforo que
emitem luz de três cores diferentes: vermelho, verde e azul. As demais cores
observadas são produzidas através da composição destas três cores básicas.

aula2.doc 5 de 10
Monitores LCD (Liquid Crystal Display) do tipo TFT (Thin-Film Transistor)
usam transistors para controlar cada pixel que pode aparecer na imagem.

Cada transistor aciona uma região do display com partículas de um cristal


em um meio líquido. Este cristal polariza a luz e, quando o potencial é
aplicado na região com o cristal líquido pelo seu transistor de controle, todos
os cristais se alinham.

Uma placa de material plástico que polariza a luz que fica na frente das
regiões de cristal líquido impedem que luz polarizada de forma diferente
passe por ela. Assim, o transistor permite ou não a passagem da luz.
Milhares de transistores são necessários para um display moderno.

aula2.doc 6 de 10
aula2.doc 7 de 10
Um display TFT colorido possui filtros para as cores vermelho, verde e azul
e transistores específicos para cada cor de cada pixel.

1.3.3 A Unidade de Processamento


Dentro da caixa do computador existe uma ou mais placas de circuito
impresso onde estão montados os circuitos eletrônicos que controlam os
dispositivos de entrada e saída (Input/Output – I/O).

Uma destas placas contém o processador e a memória. O processador é o


circuito integrado responsável por executar os programas.

A memória é composta por um conjunto de circuitos integrados e guarda os


programas e os dados necessários à sua execução.

Outros circuitos integrados em outras placas trabalham em conjunto


apoiando o processador e prestando serviços como o de relógio, portas de
comunicação com o mouse e impressora, etc.

aula2.doc 8 de 10
aula2.doc 9 de 10
1.3.4 Discos
Os discos do computador são responsáveis por armazenar os programas e
dados que devem ser preservados quando o computador está desligado.

Os discos têm este nome porque eles são, na verdade, implementados através
de discos recobertos com material magnético.

As informações são guardadas neles, bit a bit, através de campos magnéticos


gerados pelas cabeças de leitura e gravação.

aula2.doc 10 de 10
1.4 Circuitos Integrados
Quando usado em circuitos integrados (CIs) computacionais, um
transistor é simplesmente uma chave liga/desliga controlada por
eletricidade.

Um processador pode ser implementado hoje em um único CI, contendo


bilhões de transistores. O acrônimo VLSI (“Very Large Scale Integrated
Circuit”) é usado hoje para descrever CIs com um número de transistores
desta magnitude.

CIs são feitos de silício, um elemento químico encontrado na areia. Mais


de 90% da crosta da Terra é composta de silício. O silício é tetravalente e
não conduz bem a eletricidade, muito embora não seja um completo
isolante, e, por esta razão, é chamado de semicondutor.

O silício puro (99,9999999% puro) monocristalino pode ser produzido


usando processos especiais, e posteriormente pode ser fatiado em discos
finos.

Usando processos físico-químicos especiais, é possível adicionar


impurezas a lâminas de silício de modo a fazer com que pequenas áreas
se transformem em:
 Excelentes condutores de eletricidade
 Excelentes isolantes

aula3.doc 1 de 7
 Áreas que conduzem ou isolam a eletricidade de acordo com um
comando elétrico (transistores)

Um CI contém bilhões de pequenos condutores, isolantes e transistores


combinados em um único dispositivo. A base quântica do funcionamento
dos transistores de CIs modernos (MOSFETs), que são os elementos
ativos dos CIs, é a junção PN.

O transistor MOSFET é a base dos CIs que implementam processadores,


memórias e outros componentes dos computadores modernos. Ele é
implementado por meio da adição de impurezas específicas em diferentes
partes de um cristal de silício, e da adição de uma região de controle
denominada gate.

Na figura abaixo, é mostrado como pode ser implementado um inversor


lógico empregando a tecnologia de fabricação de CIs denominada
CMOS.

aula3.doc 2 de 7
Se nenhuma voltagem é aplicada ao gate, o transistor não conduz
eletricidade (off-state).

Se uma voltagem apropriada é aplicada ao gate, o transistor conduz


eletricidade (on-state).

aula3.doc 3 de 7
Este funcionamento do transistor é viabilizado por processos físicos
(quânticos) que ocorrem na junção P-N.

Transistores MOSFET pode ser do tipo P ou do tipo N e eles podem ser


combinados em um mesmo CI, permitindo a implementação de todos os
dispositivos lógicos necessários para se construir um processador.

Transistores, fios interligando-os e isolantes são implementados em CIs


por meio de um processo de fabricação que usa “mascaras”, uma para
cada camada usada em sua implementação.

aula3.doc 4 de 7
Neste processo, uma camada de material foto-sensível é colocada sobre o
silício e uma máscara, contendo os padrões geométricos dos componentes
dos transistores e suas interligações, é colocada entre uma fonte de luz e o
silício.

As partes do material foto-sensível atingidas pela luz são afetadas por ela
e removidas quimicamente. Em seguida as partes expostas do silício
recebem impurezas ou metal vaporizado em um forno – com o calor, as
impurezas penetram o silício ou o metal vaporizado adere a ele.

O mesmo processo é repetido para implementar outras camadas.

O processo de fabricação de CIs começa com um lingote de cristal de


silício com um diâmetro que tem variado de acordo com a evolução da
tecnologia.

Estes lingotes são cortados em fatias, ou “wafers”, de não mais que 0.1”
(2,5mm) de espessura. Estes wafers passam pela série de processos físico-
químicos que constróem transistores, condutores e isolantes em suas
superfícies que, em conjunto, formam CIs.

aula3.doc 5 de 7
A figura abaixo mostra uma foto bastante ampliada de um CI.

É virtualmente impossível fabricar um wafer inteiro perfeito.

Qualquer falha no wafer ou em qualquer dos processos porque ele passa,


mesmo que microscópica, pode resultar no mau funcionamento do CI
correspondente ao ponto da falha.

A estratégia mais simples para lidar com estas imperfeições é colocar


vários CIs em um único wafer. Assim, as imperfeições vão afetar apenas
alguns CIs, e não o wafer todo.

O “yield” do processo de fabricação é definido como a porcentagem dos


CIs bons do total de CIs no wafer.

aula3.doc 6 de 7
A figura abaixo mostra a foto de dois waffers com vários CIs em cada
um. Observe que o waffer mais ao fundo na foto tem um menor número
de CIs, mas estes são maiores.

aula3.doc 7 de 7
2. Sobre o Desempenho dos Computadores
2.1 Comparando o Desempenho de Dois Computadores
Sejam X e Y dois computadores. X é mais rápido que Y se:

Tempo de execução em X
_____________________ < 1

Tempo de execução em Y

O desempenho de um computador X pode ser definido como:

1
_____________________

Tempo de execução em X

A unidade de medida de desempenho computacional mais importante é o


tempo.

2.2 Medindo o Desempenho


Muitas vezes, o tempo de execução de um programa não é fácil de medir.
Em um sistema multiprogramado (sistema onde vários programas,
eventualmente de usuários, compartilham a mesma Central Processing Unit
– CPU (Unidade Central de Processamento)), o tempo de execução de um
programa pode ser descrito em termos de user CPU time (tempo de unidade
central de processamento gasto pelo usuário), system CPU time (tempo de
unidade central de processamento gasto pelo sistema), I/O (Input/Output)
time (tempo gasto com entrada/saída), etc.

Além de existirem várias formas diferentes de se medir o tempo de


execução, a própria escolha dos programas de teste pode ser complexa.
Quais programas irão de fato testar o desempenho de um computador?

2.3 Programas para Medir Desempenho


Os programas tipicamente usados para medir desempenho podem ser
divididos em: Programas Reais, Kernels, Toy Benchmarks e Synthetic
Benchmarks.

aula4.doc 1 de 9
Exemplos adequados de programas reais para medir desempenho são
programas que usamos no dia-a-dia, como compiladores por exemplo.

Kernels são programas contendo apenas um trecho de código representativo


de uma parte freqüentemente executada por programas típicos. Um exemplo
de Kernel é a multiplicação de matrizes.

Toy benchmarks são programas reais; contudo, bem simples.

Synthetic Benchmarks são programas artificiais. Eles não fazem nada útil e
são criados apenas para medida de desempenho.

Programas para medida de desempenho podem ser ordenados em termos de


adequação na ordem em que eles foram apresentados: os mais adequados são
os programas reais e os menos adequados são os Synthetic Benchmarks.

2.4 Benchmark Suites


Um Benchmark Suite é um conjunto de programas de avaliação.

A Standard Performance Evaluation Corporation (SPEC) lançou várias


benchmark suites: SPEC89, SPEC92, SPEC95, SPEC2000 e SPEC2006.

Estas benchmark suites são compostas por programas reais, escolhidos para
serem representativos de programas que tipicamente demandam muita CPU
e pouco I/O. Os programas inteiros do SPEC95 são:

099.go A go-playing program.


124.m88ksim A chip simulator for the Motorola 88100 microprocessor.
126.gcc Based on the GNU C compiler version 2.5.3.
129.compress A in-memory version of the common UNIX utility.
130.li Xlisp interpreter.
132.ijpeg Image compression/decompression.
134.perl An interpreter for the Perl language.
147.vortex An object oriented database.

aula4.doc 2 de 9
Os programas de ponto-flutuante do SPEC95 são:

101.tomcatv Vectorized mesh generation.


102.swim Shallow water equations.
103.su2cor Monte-Carlo method.
104.hydro2d Navier Stokes equations.
107.mgrid 3d potential field.
110.applu Partial differential equations.
125.turb3d Turbulence modeling.
141.apsi Weather prediction.
145.fpppp From Gaussian series of quantum chemistry benchmarks.
146.wave5 Maxwell's equations.

Os programas inteiros do SPEC2000 são:


Benchmark Language Category
164.gzip C Compression
175.vpr C FPGA Circuit Placement and Routing
176.gcc C C Programming Language Compiler
181.mcf C Combinatorial Optimization
186.crafty C Game Playing: Chess
197.parser C Word Processing
252.eon C++ Computer Visualization
253.perlbmk C PERL Programming Language
254.gap C Group Theory, Interpreter
255.vortex C Object-oriented Database
256.bzip2 C Compression
300.twolf C Place and Route Simulator

Os programas ponto-flutuante do SPEC2000 são:


Benchmark Language Category
168.wupwise Fortran 77 Physics / Quantum Chromodynamics
171.swim Fortran 77 Shallow Water Modeling
172.mgrid Fortran 77 Multi-grid Solver: 3D Potential Field
173.applu Fortran 77 Parabolic / Elliptic Partial Differential Equations
177.mesa C 3-D Graphics Library
178.galgel Fortran 90 Computational Fluid Dynamics

aula4.doc 3 de 9
179.art C Image Recognition / Neural Networks
183.equake C Seismic Wave Propagation Simulation
187.facerec Fortran 90 Image Processing: Face Recognition
188.ammp C Computational Chemistry
189.lucas Fortran 90 Number Theory / Primality Testing
191.fma3d Fortran 90 Finite-element Crash Simulation
200.sixtrack Fortran 77 High Energy Nuclear Physics Accelerator Design
301.apsi Fortran 77 Meteorology: Pollutant Distribution

Os programas inteiros do SPEC2006 são:


Application
Benchmark Language Brief Description
Area
400.perlbench C Programming Derived from Perl V5.8.7. The
Language workload includes SpamAssassin,
MHonArc (an email indexer), and
specdiff (SPEC's tool that checks
benchmark outputs).

401.bzip2 C Compression Julian Seward's bzip2 version


1.0.3, modified to do most work
in memory, rather than doing I/O.

403.gcc C C Compiler Based on gcc Version 3.2,


generates code for Opteron.

429.mcf C Combinatorial Vehicle scheduling. Uses a


Optimization network simplex algorithm
(which is also used in commercial
products) to schedule public
transport.

445.gobmk C Artificial Plays the game of Go, a simply


Intelligence: described but deeply complex
Go game.

456.hmmer C Search Gene Protein sequence analysis using

aula4.doc 4 de 9
Sequence profile hidden Markov models
(profile HMMs)

458.sjeng C Artificial A highly-ranked chess program


Intelligence: that also plays several chess
chess variants.

462.libquantum C Physics / Simulates a quantum computer,


Quantum running Shor's polynomial-time
Computing factorization algorithm.

464.h264ref C Video A reference implementation of


Compression H.264/AVC, encodes a
videostream using 2 parameter
sets. The H.264/AVC standard is
expected to replace MPEG2

471.omnetpp C++ Discrete Uses the OMNet++ discrete event


Event simulator to model a large
Simulation Ethernet campus network.

473.astar C++ Path-finding Pathfinding library for 2D maps,


Algorithms including the well known A*
algorithm.

483.xalancbmk C++ XML A modified version of Xalan-C+


Processing +, which transforms XML
documents to other document
types.

Os programas ponto-flutuante do SPEC2006 são:


Benchmark Language Application Area Brief Description
410.bwaves Fortran Fluid Dynamics Computes 3D transonic
transient laminar viscous
flow.

416.gamess Fortran Quantum Gamess implements a wide


Chemistry. range of quantum chemical

aula4.doc 5 de 9
computations. For the SPEC
workload, self-consistent
field calculations are
performed using the
Restricted Hartree Fock
method, Restricted open-
shell Hartree-Fock, and
Multi-Configuration Self-
Consistent Field

433.milc C Physics / A gauge field generating


Quantum program for lattice gauge
Chromodynamics theory programs with
dynamical quarks.

434.zeusmp Fortran Physics / CFD ZEUS-MP is a


computational fluid
dynamics code developed at
the Laboratory for
Computational Astrophysics
(NCSA, University of
Illinois at Urbana-
Champaign) for the
simulation of astrophysical
phenomena.

435.gromacs C, Biochemistry / Molecular dynamics, i.e.


Fortran Molecular simulate Newtonian
Dynamics equations of motion for
hundreds to millions of
particles. The test case
simulates protein Lysozyme
in a solution.

436.cactusADM C, Physics / General Solves the Einstein


Fortran Relativity evolution equations using a
staggered-leapfrog
numerical method

aula4.doc 6 de 9
437.leslie3d Fortran Fluid Dynamics Computational Fluid
Dynamics (CFD) using
Large-Eddy Simulations
with Linear-Eddy Model in
3D. Uses the MacCormack
Predictor-Corrector time
integration scheme.

444.namd C++ Biology / Simulates large


Molecular biomolecular systems. The
Dynamics test case has 92,224 atoms
of apolipoprotein A-I.

447.dealII C++ Finite Element deal.II is a C++ program


Analysis library targeted at adaptive
finite elements and error
estimation. The testcase
solves a Helmholtz-type
equation with non-constant
coefficients.

450.soplex C++ Linear Solves a linear program


Programming, using a simplex algorithm
Optimization and sparse linear algebra.
Test cases include railroad
planning and military airlift
models.

453.povray C++ Image Ray-tracing Image rendering. The


testcase is a 1280x1024 anti-
aliased image of a landscape
with some abstract objects
with textures using a Perlin
noise function.

454.calculix C, Structural Finite element code for


Fortran Mechanics linear and nonlinear 3D
structural applications. Uses
the SPOOLES solver

aula4.doc 7 de 9
library.

459.GemsFDTD Fortran Computational Solves the Maxwell


Electromagnetics equations in 3D using the
finite-difference time-
domain (FDTD) method.

465.tonto Fortran Quantum An open source quantum


Chemistry chemistry package, using an
object-oriented design in
Fortran 95. The test case
places a constraint on a
molecular Hartree-Fock
wavefunction calculation to
better match experimental
X-ray diffraction data.

470.lbm C Fluid Dynamics Implements the "Lattice-


Boltzmann Method" to
simulate incompressible
fluids in 3D

481.wrf C, Weather Weather modeling from


Fortran scales of meters to
thousands of kilometers.
The test case is from a 30km
area over 2 days.

482.sphinx3 C Speech A widely-known speech


recognition recognition system from
Carnegie Mellon University

aula4.doc 8 de 9
2.5 Reportando Medidas de Desempenho
O mais importante aspecto a ser observado quando estamos reportando
desempenho é reprodutibilidade – deve-se listar tudo que outro
experimentador vai precisar para duplicar os resultados sendo reportados.

Assim, no caso de computadores, o clock do processador, o tamanho do


cache, o tamanho da memória, a velocidade de acesso aos discos, o
compilador, as flags utilizadas na compilação, etc, devem ser indicados em
um relatório de desempenho.

aula4.doc 9 de 9
2.6 Comparando e Sumarizando Desempenho
Mesmo tomando o cuidado de indicar todos os parâmetros relevantes,
dúvidas podem surgir quando comparamos ou sumarizamos desempenho.

Computador A Computador B Computador C


Programa P1 (seg.) 1 10 20
Programa P2 (seg.) 1000 100 20
Tempo Total 1001 110 40

Usando diferentes definições de “mais rápido”, temos:


 A é 10 vezes mais rápido que B para o programa P1.
 No entanto, B é 10 vezes mais rápido que A para o programa P2.
 Se tomarmos o tempo total para executar P1 e P2 como medida de
desempenho, C é o computador mais rápido; contudo, ele é 20 vezes mais
lento que A na execução do programa P1.
 Etc…

A melhor forma de sumarizar desempenho é o tempo total de execução.


Assim, quando comparando o desempenho de computadores para um
conjunto de programas (benchmark suite) podemos sumarizar o desempenho
das seguintes formas:

 Média aritmética dos tempos de execução – é equivalente à soma dos


tempos de execução
1 n
Média aritmética de Tempo (i,n) =  Tempo i
n i 1

 Se o desempenho é expresso como uma taxa (rate), a média harmônica


acompanha a soma dos tempos de execução.
n
n
Média harmônica de Taxa (i,n) = 1
 Taxa
i 1 i

Computador A Computador B Computador C


Programa P1 (seg.) 1 10 20
Programa P2 (seg.) 1000 100 20
Tempo Total 1001 110 40
Média Aritmética 500,5 55,0 20,0
Média Harmônica 1,998 18,181 20,000

aula5.doc 1 de 4
aula5.doc 2 de 4
 A média geométrica dos tempos normalizados é também utilizada para
sumarizar desempenho.
n
Média geométrica do Tempo Normalizado (i,n) = n
Tnormalizado i
i 1

Uma característica da média geométrica dos tempos normalizados é que ela


melhora igualmente se um benchmark do suite é melhorado de 2 para 1 ou
se um outro é melhorado de 2000 para 1000. A normalização pode causar
interpretações errôneas da média aritmética também, como mostrado abaixo.

Normalizado por A
Computador A Computador B Computador C
Programa P1 (seg.) 1.0 10.0 20.0
Programa P2 (seg.) 1.0 0.1 0.02
Média Aritmética 1.0 5.05 10.01
Média Geométrica 1.0 1.0 0.63
Tempo Total 1.0 0.11 0.04

2.7 Princípios Quantitativos para o Projeto de Computadores


O principal princípio a ser seguido é: Torne mais rápidas as tarefas mais
freqüentes. Este princípio pode ser quantificado pela lei de Amdahl:

A quantidade de melhoria de desempenho provida por um atributo da


arquitetura é limitada pela quantidade de tempo que este atributo é
utilizado.

A lei de Amdahl define o speedup que pode ser obtido por um atributo
particular de um sistema.

Tempo de execução da tarefa inteira sem usar o novo atributo


Speedup = _____________________________________________________________

Tempo de execução da tarefa inteira usando o novo atributo quando possível

aula5.doc 3 de 4
2.8 O Tempo de Execução de um Programa
Se apenas o processador (CPU) é levado em consideração, o tempo de
execução de um programa pode ser definido como:

 Tempo de CPU de um Programa = Ciclos de Clock despendidos no


Programa x Tempo de um Ciclo

 Ou, Tempo de CPU de um Programa = Ciclos de Clock despendidos no


Programa / Freqüência de Clock

Exercício: Um programa roda em 10 segundos em um computador A, que


tem um clock de 100MHz. Se um computador B gasta 1.2 vezes mais ciclos
de clock para rodar o mesmo programa, mas roda este programa em 6
segundos, qual é a freqüência de clock do computador B?

10s = x_ciclos / 100MHz; x_ciclos = 10s x 100MHz


6s = 1.2 x x_ciclos / x_MHz; x_MHz = 1.2 x 10s x 100MHz / 6s
x_MHz = 200MHz

aula5.doc 4 de 4
2.9 O Desempenho da CPU
O desempenho de uma CPU na execução de um programa pode ser medido
através do tempo que esta CPU gasta para executar este programa. Quanto
menor o tempo, maior o desempenho. O tempo de execução de um
programa, T, pode ser expresso por:

T (segundos) = N (instrução) x C (ciclo/instrução) x S (segundos/ciclo)

onde N é o número total de instruções executadas, C (ou clocks por ciclo de


máquina – “Clocks per Cycle” (CPI)) é a média do número de ciclos por
instrução e S é o número de segundos por ciclo.

Em uma primeira aproximação, N, C e S são afetados primariamente: pela


capacidade de otimização do compilador; pela arquitetura do processador e
de seu conjunto de instruções; e pela tecnologia empregada na
implementação da máquina.

Nós podemos medir o número de instruções executadas em um programa


com ferramentas de software conhecidas como “execution profilers” ou
através de simuladores da arquitetura, por exemplo.

O número de segundos (na verdade, nano ou pico segundos hoje em dia) por
ciclo de máquina normalmente é indicado no manual da máquina.

O CPI, por outro lado, depende de vários fatores, tais como a organização de
memória da máquina, o conjunto de instruções do processador, a mistura de
instruções executadas pela aplicação, a arquitetura do processador, etc.

Exercício: Um projetista de compiladores está tentando decidir sobre qual


seqüência de instruções ele deve utilizar para implementar um comando de
uma linguagem de alto nível. Ele dispõe das seguintes informações:
 Existem duas seqüências de instruções possíveis para implementar o
comando. Seqüência 1: duas instruções A, uma B e duas C; seqüência 2:
quatro A, uma B e uma C.
 Na máquina alvo, a instrução A tem o CPI de 1, a instrução B tem CPI de
2 e a C, de 3.
1 - Que seqüência de código executa mais instruções? 2 - Qual seqüência
executa mais rápido? 3 - Qual é o CPI para cada seqüência?

Respostas:

aula6.doc 1 de 3
1: seq1 = 2+1+2 = 5 instruções; seq2 = 4+1+1 = 6 instruções, logo, resposta = seq2
2: seq1 = 2x1 + 1x 2 + 2x3 = 10 ciclos; seq2 = 4x1 + 1x2 + 1x3 = 9 ciclos
3: CPI(seq1) = 10 ciclos / 5 instruções = 2 CPI; CPI(seq2) = 9 / 6 = 1.5 CPI

2.10 Unidades de Medida de Desempenho


2.10.1 MIPS – “Million Instructions per Second
A unidade MIPS pode ser definida como:
MIPS = Freqüencia de Clock / CPIx106
O Tempo de Execução de um programa pode ser definido em função da
unidade MIPS:
Tempo de execução = No. de Instruções / MIPSx106
MIPS é uma unidade de performance ruim porque: Primeiro, nós não
podemos comparar computadores com conjuntos de instruções diferentes
usando MIPS.

Isso porque, o número de instruções necessário para executar um


determinado programa nestes dois computadores vai quase certamente
diferir e, se a freqüência de clock for a mesma, o computador que demora
mais para executar o programa aparentemente terá o mesmo desempenho do
que demora menos.

Segundo, a quantidade de MIPS varia entre programas em um mesmo


computador, assim, uma máquina não pode ter uma performance única em
termos de MIPS.

Terceiro, a medida de desempenho em termos de MIPS pode variar


inversamente com o desempenho verdadeiro, como podemos demonstrar
resolvendo o exercício abaixo.

Exercício: Sabendo que, para um programa específico e para um computador


com clock de 100MHz, o compilador 1 gera 5 milhões de instruções do tipo
A, 1 milhão do tipo B e 1 milhão do tipo C, e que o compilador 2 gera 10
milhões do tipo A, 1 milhão do tipo B e 1 milhão do tipo C, e que o CPI de
cada instrução é igual ao do exercício anterior, mostre que a medida de
performance em termos de MIPS pode variar inversamente com a
performance verdadeira. Suponha que cada instrução gerada é executada
apenas uma vez.

2.10.2 MFLOPS – “Million Floating-Point Operations per Second


A unidade MFLOPS é definida como:

aula6.doc 2 de 3
MFLOPS = número de operações de ponto flutuante em um programa
/ tempo de execução x 106
Embora, a primeira vista, o número de operações de ponto flutuante de um
programa pareça ser o mesmo para diferentes computadores, ele não é
devido ao fato de diferentes computadores terem conjuntos de instruções de
ponto flutuante diferentes. Por esse e outros problemas, MFLOPS também
não é uma boa unidade de medida de performance.

2.10.3 SPEC
Medir o tempo de execução usando um conjunto de programas de uso
generalizado, com entradas conhecidas, e sob circunstâncias conhecidas e
especificadas é a melhor maneira de se medir o desempenho de um
computador. As unidades de medidas SPECint e SPECfp são consideradas,
atualmente, boas unidades de medida de desempenho.

aula6.doc 3 de 3
3 Instruções: A Linguagem da Máquina
Para controlar o hardware de um computador você precisa falar a linguagem
dele. As palavras que um computador entende são chamadas instruções e seu
vocabulário é chamado conjunto de instruções (“instruction set”).

Instruções de máquina devem ser simples para: (i) facilitar o projeto e


implementação do hardware, (ii) ser de rápida execução pelo hardware, e
(iii) facilitar o projeto e a implementação do compilador. Nós estudaremos o
conjunto de instruções dos processadores MIPS.

3.1 Operações Básicas


Todos os computadores têm que ser capazes de fazer operações aritméticas.
De acordo com a Instruction Set Architecture (ISA) dos processadores
MIPS,

add a, b, c

instrui o processador a somar b e c e colocar o resultado em a. Se


precisarmos somar quatro valores, b, c, d, e, precisamos de mais instruções:

add a, b, c
add a, a, d
add a, a, e

O número natural de operandos para uma operação como a adição é três.


Assim, para tornar o hardware dos processadores mais simples, três
operandos sempre têm que ser usados para operações deste tipo na maioria
dos processadores atuais.

Hardware para instruções com um número de operandos variável é mais


complicado e freqüentemente mais lento. Esta situação destaca o primeiro de
quatro princípios básicos do projeto de hardware:

Princípio 1: Regularidade favorece a simplicidade

Ou seja, é bom que a maioria ou todas as instruções tenham um número de


operandos igual, pois isso tornará a implementação do hardware do
processador mais simples.

aula7.doc 1 de 4
Um compilador recebe um programa em linguagem de alto nível e gera
como saída um programa em linguagem de máquina. Uma forma mais
legível de linguagem de máquina é a linguagem de montagem, ou “assembly
language”.

A linha de código em linguagem C abaixo

f = (g + h) – (i + j); /* isto é uma linha de um programa em C */

pode ser traduzida para o trecho de código em assembly abaixo

add t0, g, h # a variável temporária t0 contém g + h


add t1, i, j # a variável temporária t1 contém i + j
sub f, t0, t1 # f recebe t0 – t1, ou (g + h) – (i + j)

3.2 Operandos
Operandos de instruções de máquina não podem ser de qualquer tipo: na ISA
MIPS eles têm que ser um dos registradores do processador, uma constante
ou o conteúdo de uma posição de memória.

O tamanho dos registradores da ISA MIPS é 32 bits e grupos de 32bits nesta


ISA são chamados de “word”.

Existem apenas 32 registradores na ISA MIPS para operandos inteiros, cujos


nomes são $0, $1, … $31, e operações lógicas e aritméticas apenas ocorrem
entre estes registradores. A razão para este pequeno número de registradores
é o segundo princípio para o projeto de hardware:

Princípio 2: Menor é mais rápido

Associar variáveis do programa em alto nível a registradores é tarefa do


compilador. No último exemplo da seção anterior, as variáveis f, g, h, i e j
podem ser associadas aos registradores $16, $17, $18, $19 e $20,
respectivamente:

add $8, $17, $18 # o registrador $8 contém g + h


add $9, $19, $20 # o registrador $9 contém i + j
sub $16, $8, $9 # f recebe $8 – $9, ou (g + h) – (i + j)

aula7.doc 2 de 4
Estruturas de dados maiores que 32 bits, como matrizes por exemplo, são
mantidas na memória. Para operar sobre elas, o processador tem que trazê-
las, por partes, para seus registradores, realizar as operações, e transferi-las
por partes de volta para a memória.

Exemplo:
g = h + A[i]; /* Código C. A é um vetor de bytes. */

add $8, $9, $19 # $9 contém o endereço de A na memória e $8, um


# registrador temporário, recebe A + i
lb $8, 0($8) # o registrador temporário $8 recebe A[i]
add $17, $18, $8 # g = h + A[i]

Neste trecho de código, a primeira instrução add calcula o endereço na


memória do elemento i do vetor de bytes A.

O registrador $9 contém o endereço do primeiro elemento de A (o elemento


de ordem 0, na linguagem C) e o registrador $19 o valor da variável i.

A instrução lb (“load byte”) lê a posição de memória cujo endereço é 0 (uma


constante) mais o valor do registrador $8 – o processador soma a constante
0, especificada pelo compilador, com o endereço em $8 e usa o resultado
como endereço para a leitura de A[i] da memória.

A memória do computador é um grande vetor – no caso da ISA MIPS ela


pode ter 232 bytes. Se, como no exemplo anterior, o vetor A é um vetor de
bytes, a variável i ($19) tem que ser incrementada de 1 em assembly quando
incrementada de 1 em C.

Se A é um vetor de words, i também deve ser incrementada de 1 em C, mas


deve ser incrementada de 4 em assembly para avançar de 1 no vetor. Além
disso, uma instrução que lê words da memória deve ser usada: lw ou “load
word”. Na ISA MIPS, existe também uma instrução para ler grupamentos de
16 bits: lh, ou “load half word”.

aula7.doc 3 de 4
Para escrever dados na memória a instrução “store” é utilizada:

A[i] = h + A[i];

add $7, $9, $19 # o registrador temporário $7, recebe A + i


lb $8, 0($7) # $8 recebe A[i]
add $8, $18, $8 # $8 = h + A[i]
sb $8, 0($7) # guarda h + A[i] em A[i]

Existem, também, as instruções sw e sh.

Freqüentemente, programas possuem mais que 32 variáveis. Assim, os


compiladores têm que otimizar o uso dos registrados, transferindo variáveis
da memória para registradores e vice-versa quando necessário.

Na verdade, registradores existem porque o acesso a eles é mais rápido do


que à memória (é possível implementar uma ISA sem registradores).

aula7.doc 4 de 4
3.3 O Formato das Instruções
Para fornecer instruções ao computador temos que usar códigos em binário
(números na base 10 são ditos decimais, na base 2, binários). Para facilitar a
leitura humana de números binários, a base 16 – ou números em
hexadecimal – é, muitas vezes, utilizada:

Decimal Binário Hexadecimal


0 0000 0
1 0001 1
2 0010 2
3 0011 3
4 0100 4
5 0101 5
6 0110 6
7 0111 7
8 1000 8
9 1001 9
10 1010 A
11 1011 B
12 1100 C
13 1101 D
14 1110 E
15 1111 F

Instruções são mantidas dentro do computador no formato binário e podem


ser representadas por números. Na verdade, cada campo de uma instrução
pode ser representado por um número:

add $8, $17, $18

nome do campo op rs rt rd shamt Funct


ordem 1 2 3 4 5 6
No. bits 6 5 5 5 5 6
decimal 0 17 18 8 0 32
binario 00 0000 1 0001 1 0010 0 1000 0 0000 10 0000
hexadecimal 00 11 12 08 00 20

O primeiro e o último campo acima, combinados, informam ao processador


que a instrução é um add.

Os campos 2 e 3 informam quais são os registradores com os operandos


fonte.

O campo 4, o registrador com o operando destino.

O campo 5 não é utilizado nesta instrução e recebe zero.

aula8.doc 1 de 3
Cada campo tem um nome:
 op = operação (também conhecido como opcode);
 rs e rt = 1o. e 2o. registradores fonte, respectivamente;
 rd = registrador destino;
 shamt = tamanho do deslocamento (shift amount, usando em
instruções de deslocamento, que veremos adiante); e
 funct = função (function, indica uma variação específica da instrução
indicada em op).

Todas as instruções da MIPS ISA têm 32 bits de largura.

Uma instrução lw precisa especificar dois registradores e uma constante.


Para acomodar estas informações em 32 bits, um formato diferente de
instrução é utilizado:

lw $8, 1200($19)

nome op rs rt Address
No. bits 6 5 5 16
decimal 35 19 8 1200

Note que o significado do campo rt mudou – nesta instrução, ele indica um


registrador que é escrito (registrador destino). Para o processador saber qual
é o formato da instrução, ele consulta o campo op.

No caso da lw, um compromisso teve que ser feito entre a largura da


instrução, o número de campos e o tamanho da constante address da
instrução lw. Isto ilustra o terceiro princípio do projeto de hardware:

Princípio 3: Bons projetos exigem compromissos.

aula8.doc 2 de 3
Exemplo de Exercício: Traduza o trecho de código em C abaixo para
linguagem assembly e para linguagem de máquina. O endereço do vetor de
words, A, está no registrador $17, o registrador $18 contem o valor da
variável h e $19 contem 4 vezes i.

A[i] = h + A[i];

Assembly
add $7, $17, $19 # o temporário $7 recebe A + (i * 4)
lw $8, 0($7) # o registrador temporário $8 recebe A[i]
add $8, $18, $8 # o registrador temporário $8 recebe h + A[i]
sw $8, 0($7) # escreve h + A[i] em A[i]

linguagem de máquina
decimal 0 17 19 7 0 32
decimal 35 7 8 0
decimal 0 18 8 8 0 32
decimal 43 7 8 0

binário 00 0000 1 0001 1 0011 0 0111 0 0000 10 0000


binário 10 0011 0 0111 0 1000 0000 0000 0000 0000
binário 00 0000 1 0010 0 1000 0 1000 0 0000 10 0000
binário 10 1011 0 0111 0 1000 0000 0000 0000 0000

A linguagem assembly é, obviamente, muito mais fácil de usar que a


linguagem de máquina.

Além de traduzir os símbolos para números, assemblers tratam variações de


certas instruções como se elas fossem instruções independentes.

Por exemplo, o hardware de um processador MIPS sempre lê zero do


registrador $0, e escritas em $0 não tem efeito. Assim, assemblers traduzem
a instrução

move $8, $18 # $8 recebe o valor de $18


para
add $8, $0, $18 # $8 recebe 0 + $18

A pseudo instrução move (ou freqüentemente em assemblers mov) facilita a


o entendimento do que o programa de fato faz.

aula8.doc 3 de 3
O funcionamento dos computadores é baseado em dois princípios chave:

1. Instruções são representadas como números.


2. Programas podem ser guardados em memória, e podem ser lidos e
escritos como números.

Em conjunto, estes princípios definem o conceito de programa armazenado.

memória
Editor (código de máquina)

processador
Texto em linguagem C

compilador (código de máquina)

Folha de pagamento (dados)

aula8.doc 4 de 3
3.4 Instruções de Desvio
Um processador é capaz de tomar decisões baseado nos dados de entrada e
nos valores computados durante a execução das instruções.

Em linguagens de alto nível, o comando if (se) pode ser usado para tomada
de decisões. Na MIPS ISA, a instrução abaixo é usada para tomar uma
decisão de acordo com o conteúdo de dois registradores.

beq reg1, reg2, endereço

Esta instrução manda o processador ir para o endereço especificado se o


conteúdo de reg1 for igual ao de reg2.

Exercício: No fragmento de código em C abaixo, f, g, h, i e j são variáveis.

if (i == j) goto L1;
i = g + h;
L1: f = f – i;

Assumindo que as cinco variáveis correspondem aos cinco registradores


$16, … , $20, qual o código assembly equivalente ao fragmento em C
acima?

Resposta:

beq $19, $20, L1


add $19, $17, $18
L1: sub $16, $16, $19

Instruções são guardadas na memória dos computadores do mesmo modo


que outras words de dados. Assim, instruções têm endereços de memória do
mesmo modo que words de dados.

O label L1 acima corresponde ao endereço da instrução sub; os assemblers


cuidam de transformar labels em endereços de memória ou outros valores
eventualmente necessários para simplificar o trabalho do programador.

aula9.doc 1 de 3
Outro exemplo: i == j i == j? i != j
C
Else:
if (i == j)
f = g + h; f=g+h f=g-h
else
f = g – h;
Exit:
Assembly
bne $19, $20, Else
add $16, $17, $18
j Exit
Else: sub $16, $17, $18
Exit:

Mais um exemplo:
Faça um programa em C que some os salários de todos os funcionários de
uma firma. A firma tem 10 funcionários e os salários estão guardados no
vetor SAL. Traduza o programa para o assembly da MIPS ISA.

C
total = 0;
i = 0;
while (i != 10) /* enquanto i for diferente de 10 */
{
total = total + SAL[i];
i = i + 1;
}

Assembly: ($8 é total, $9 i, e $12 o endereço do primeiro elemento de SAL)


add $8, $0, $0 # total = 0
add $9, $0, $0 #i=0
add $10, $0, 10*4 # $10 contem a constante 10*4
While: beq $9, $10, Exit # while
add $11, $12, $9 # $12 contem o endereço de SAL
lw $11, 0($11) # $11 = SAL[i]
add $8, $8, $11 # total = total + SAL[i]
add $9, $9, 4 #i=i+1
j While
Exit:

aula9.doc 2 de 3
A MIPS ISA inclui uma instrução para fazer testes sobre o conteúdo de
registradores – a instrução slt, ou “set on less than” (ligue se menor que).

Esta instrução testa o conteúdo de dois registradores e torna o conteúdo de


um terceiro igual a 1 ou 0 se o primeiro é menor ou não é menor que o
segundo, respectivamente. Por exemplo,

slt $8, $19, $20

torna o conteúdo do registrador $8 igual a 1 se o conteúdo de $19 é menor


que o de $20; caso contrario, $8 recebe 0.

Compiladores e montadores MIPS usam as instruções slt, beq, bne e o


conteúdo fixo, zero, de $0 para fazer todos os testes. Exemplo:

slt $1, $16, $17 # $1 recebe 1 se $16 < $17


bne $1, $0, Less # o desvio é tomado se $1 != 0, isto é, se $16 < $17

Muitos montadores para a ISA MIPS transformam a pseudo-instrução blt


para a seqüência acima.

aula9.doc 3 de 3
Muitas linguagens possuem comando equivalente ao “switch” do C:

switch (k)
{
case 0: /* k == 0 */
f = i + j;
break;
case 1: /* k == 1 */
f = g + h;
break;
case 2: /* k == 2 */
f = g - h;
break;
case 3: /* k == 3 */
f = i - j;
break;
default:
f = 0;
}

A instrução jr (jump register), que desloca o fluxo de controle para o


endereço guardado em um registrador, pode ser usada para implementar o
comando switch.

aula10.doc 1 de 7
Exemplo: Assumindo que as variáveis f, g, …, k estão alocadas nos
registradores $16, …, $21 e que o vetor JumpTable contém o endereço dos
comandos case acima, o código assembly abaixo é equivalente ao código C
acima:

blt $21, $0, Default # se k < 0, vai para Default


move $10, 4 # o temporário $10 = 4
bge $21, $10, Default # se k >= 4, vai para Default
sll $9, $21, 2 # o temporário $9 = k*4
lw $8, JumpTable($9) # $8 recebe o endereço do Case
# em JumpTable[k]
jr $8 # salta para o Case apropriado
Case0: add $16, $19, $20
j Exit
Case1: add $16, $17, $18
j Exit
Case2: sub $16, $17, $18
j Exit
Case3: sub $16, $19, $20
j Exit
Default: move $16, 0
j Exit

JumpTable: .word Case0, Case1, Case2, Case3


Exit:

aula10.doc 2 de 7
Procedimentos e funções são utilizados para tornar os programas mais
legíveis e para permitir a fácil reutilização de código (a linguagem C só tem
funções).

Exemplo:
#include <stdio.h>

int maior (int a, int b)


{
if (a > b)
return (a);
else
return (b);
}

int main ()
{
int x, a, b;

a = 1;
b = 2;
x = maior (a, b);
printf (“a = %d, b = %d, maior = %d\n”, a, b, x);
return (0);
}

A função printf faz parte de um conjunto de funções que já vem junto com o
compilador.

Ela imprime na saída o que está entre aspas, a menos que apareçam os
caracteres % e \.

Nestes casos, ela imprime as variáveis listadas após as aspas conforme os


tipos especificados (o d indica inteiro) ou o caracter especial \{caracter}.
Para imprimir % usa-se %% e para imprimir \ usa-se \\. Veja um manual de
C para mais detalhes sobre a printf e outras funções padrão.

aula10.doc 3 de 7
Para permitir a chamada de procedimentos em assembly, são necessárias
uma (i) instrução para desviar para o inicio de uma função e (ii) outra para
retornar para a instrução logo após a instrução de desvio, após a execução da
função.

A instrução da MIPS ISA que é usada para desviar para o início da função é
instrução “jump-and-link” ou jal:

Função: …

jr $31 # fim de “Função”

jal Função # salta para “Função”


Seguinte: add $8, $9, $10 # depois que executar a função “Função”,
… # a execução continua aqui

Esta instrução salta para o endereço Função e, além disso, guarda o endereço
da instrução seguinte a ela própria (no caso acima, o endereço Seguinte) no
registrador $31.

Este endereço é chamado de “return address”, ou endereço de retorno. No


fim da execução da função, a instrução jr $31 é utilizada para retornar para o
código que chamou a função.

aula10.doc 4 de 7
Note, contudo, que, quando uma função chama outra função, o endereço de
retorno pode ser perdido. Exemplo:

C: …

jr $31 # esta instrução retorna para retB…

B: …
jal C # após este jal, $31 contém o endereço retB: retA foi
# perdido!
retB:

jr $31 # esta instrução desviaria para retB…

A: …
jal B # após este jal, $31 contém o endereço retA
retA:

jr $31

aula10.doc 5 de 7
Para evitar isso, uma estrutura de dados chamada de “stack” (pilha) é
utilizada para guardar os endereços de retorno. Por convenção da linguagem
assembly do MIPS, o registrador $29 aponta para o primeiro elemento livre
do stack e o stack cresce para baixo (endereços menores).

C: …

jr $31 # esta instrução desvia para retB…

B: …
sw $31, 0($29) # guarda o antigo valor de $31 na pilha (retA)
sub $29, $29, 4 # ajusta o ponteiro da pilha
jal C # após este jal, $31 contém o endereço retB
retB:

add $29, $29, 4 # prepara para remover um item da pilha
lw $31, 0($29) # lê o endereço de retorno anterior para $31 (retA)
jr $31 # retorna para A

A: …
sw $31, 0($29) # guarda o antigo valor de $31 na pilha
sub $29, $29, 4 # ajusta o ponteiro da pilha
jal B # após este jal, $31 contém o endereço retA
retA:

add $29, $29, 4 # prepara para remover um item da pilha
lw $31, 0($29)
jr $31

aula10.doc 6 de 7
Além de um padrão para qual registrador aponta para o stack e de como ele
cresce, outras convenções também são necessárias. Seguem algumas
convenções da linguagem assembly do MIPS:
 $1 ($at), $26 ($k0) e $27 ($k1) são reservados para o sistema operacional
e o assembler e não devem ser utilizados pelo usuário
 $4-$7 ($a0-$a3) são usados para passar os primeiros 4 argumentos para
funções (argumentos adicionais são passados via stack)
 $2, $3 ($v0, $v1) são usados para retornar valores de funções
 $8-$15, $24 e $25 ($t0-$t9) são registradores temporários “caller-saved”:
podem ser usados para guardar valores que não precisam ser mantidos
entre chamadas de função
 $16-$23 ($s0-$s7) são registradores “callee-saved”: contém valores que
devem ser preservados entre chamadas de função
 $28 ($gp) “global pointer”: aponta para as variáveis globais
 $29 ($sp) “stack pointer”: aponta para a primeira posição livre do stack
 $30 ($fp) “frame pointer”: aponta para o inicio da área de trabalho da
função corrente que acaba no stack
 $31 ($ra) contém o “return address” gerado pelas instruções jal

aula10.doc 7 de 7
No nível de linguagem de montagem, quando uma função chama outra
(caller chama callee) as convenções mencionadas tem que ser observadas
para garantir que elas operem em conjunto corretamente.

Para facilitar esta interação, um bloco de memória chamado de “stack


frame” é utilizado. Esta área de memória é utilizada para vários propósitos:
 Guardar valores passados como argumentos da função callee
 Salvar registradores cujo valor tenha que ser preservado entre chamadas
 Prover espaço para variáveis locais às funções

O stack frame consiste na área de memória entre o frame pointer, que aponta
para a primeira word do stack frame, e o stack pointer, que aponta para a
primeira word após o stack frame.

Como o stack (pilha) cresce para baixo (endereços menores), o frame pointer
contém um endereço mais alto que o stack pointer.

Como exemplo das convenções da linguagem assembly do MIPS, vamos


examinar um programa em assembly que executa o código em C abaixo.

#include <stdio.h>

int maior (int a, int b)


{
if (a > b)
return (a);
else
return (b);
}

int main ()
{
int a, b, x;

a = 1;
b = 2;
x = maior (a, b);
printf (“a = %d, b = %d, maior = %d\n”, a, b, x);
return (0);

aula11.doc 1 de 5
}

aula11.doc 2 de 5
No programa em assembly, um stack frame como o mostrado a seguir é
empregado:

Stack Frame

Antes da chamada Depois da chamada


Livre  0($sp)
$t0  4($sp)
$t1  8($sp)
 12($sp)
$fp  16($sp)
$ra  20($sp)
 24($sp)
 28($sp)
($sp)   $fp (32($sp))

Antes de uma função caller chamar uma callee, o stack pointer aponta para a
primeira posição livre do stack. Depois da chamada, uma função callee
simples prepara seu stack frame como mostrado acima, por exemplo.

A estrutura de dados stack frame permite fácil acesso a valores de interesse


usando instruções de load/store como abaixo:

sw $ra, 20($sp) # guarda o endereço de retorno para a


# função chamadora (caller) no stack
# frame
sw $fp, 16($sp) # guarda o frame pointer no stack frame

sw $t0, 4($sp) # guarda uma variável da função


# callee no stack frame

lw $ra, 20($sp) # restaura o endereço de retorno para a caller


lw $fp, 16($sp) # restaura o frame pointer da caller

Stack frames podem ser maiores e ter várias seções, dependendo da função
callee. Funções callee mais complexas requerem stack frames maiores para
guardar o conteúdo de registradores que ela precisa salvar antes de usar,
como por exemplo $s0-$s7, e suas variáveis locais que não puderem ser
alocadas em registradores, como vetores, por exemplo.

aula11.doc 3 de 5
Versão em assembly do programa em C (pode ser simulada com o programa
PCspim: (http://pages.cs.wisc.edu/~larus/spim.html):

.data # indica que o que vem abaixo são dados


MENS: .asciiz “a = %d, b = %d, maior = %d\n”

.text # indica que o que vem abaixo é código


main: subu $sp, $sp, 32 # separa um “frame” de 32 bytes no stack
# para a função chamada (callee), main
# (push)
sw $ra, 20($sp) # guarda no frame o endereço de retorno
# para a função chamadora (caller), que é o
# Sistema Operacional (S.O.)
sw $fp, 16($sp) # guarda o frame pointer do S.O.
addu $fp, $sp, 32 # ajusta o frame pointer para a função main

add $t0, $0, 1 #a=1


add $t1, $0, 2 #b=2
sw $t0, 4($sp) # separa 4($sp) para a
sw $t1, 8($sp) # separa 8($sp) para b

# Parâmetros de maior()
move $a0, $t0 # os parâmetros são passados em $a0
move $a1, $t1 # e $a1
jal maior # maior() retorna o maior em $v0

lw $t0, 4($sp) # lê a de volta para seu registrador


lw $t1, 8($sp) # lê b de volta para seu registrador

# Parâmetros de printf()
la $a0, MENS # coloca o endereço MENS em $a0
move $a1, $t0 # coloca a e b em $a1 e $a2
move $a2, $t1
move $a3, $v0 # x=maior(a, b); x é colocado em $a3
# jal printf # parâmetros em $a0, $a1, $a2, $a3

add $v0, $0, 4 # como não temos printf no simulador


# vamos usar uma syscall do pcspin
# 4 é o codigo para imprimir string
syscall

aula11.doc 4 de 5
add $v0, $0, 0 # return (0)
lw $ra, 20($sp) # restaura o $ra para o S.O.
lw $fp, 16($sp) # restaura o frame pointer do S.O.
addu $sp, $sp, 32 # remove o stack frame de main()
jr $ra # retorna para o S.O.

maior: bgt $a0, $a1, Then # $a0 contém a e $a1 contém b


Else: move $v0, $a1 # coloca b (se ele for maior) em $v0
jr $ra # retorna para main()
Then: move $v0, $a0 # coloca a (se ele for maior) em $v0
jr $ra # retorna para main()

aula11.doc 5 de 5
3.5 Endereçamento
Instruções podem ser classificadas de acordo com o número de endereços
que elas utilizam.

Não se deve esquecer que o conjunto de registradores dos processadores


constitui, de fato, uma memória (de alta velocidade) e define um espaço de
endereçamento.

Uma instrução que soma o conteúdo do registrador 1 ao do registrador 2 e


coloca o resultado no registrador 3 deve ser classificada como tendo três
endereços porque ela deve especificar quais são os três registradores que
serão utilizados. A instrução MIPS:

add r3, r1, r2 <==> r3 = r1+r2;

é uma instrução de três endereços.

Instruções de zero, um, dois e três endereços são comuns. Instruções de dois
endereços compõem grande parte da ISA IBM370. A instrução do IBM370:

AR R1, R2 <==> (Add Register) R1 = R1+R2

é exemplo de uma instrução de dois endereços. Instruções de um endereço


são comuns no 6502 (processador do Apple II). Estas instruções usam um
registrador implícito como operando: o acumulador. A instrução:

add #10 <==> A = A + 10 (A é o acumulador e #10 é a constante 10)

é uma instrução de um endereço. Instruções de zero endereço são comuns


nos processadores Transputer e operam sobre dados na pilha. A instrução:

add

remove dois elementos da pilha, soma-os, e coloca o resultado no topo da


pilha.

aula12.doc 1 de 6
3.5.1 Endereçamento de Registrador
Operandos de instruções podem ser acessados de diversas maneiras
diferentes. No modo de endereçamento conhecido como endereçamento de
registrador, o conteúdo de um registrador é o operando. Exemplo:

add $3, $2, $1

Nesta instrução, o modo de endereçamento dos três operandos é


endereçamento de registrador.

3.5.2 Endereçamento Imediato


Neste modo de endereçamento, o operando é uma constante embutida na
própria instrução (o i de addi indica este tipo de endereçamento):

addi $3, $2, 10

A constante 10 é codificada na instrução como indicado abaixo:

nome op rs rt immediate
No. bits 6 5 5 16
decimal 8 2 3 10

Constantes são normalmente de pequeno valor em programas típicos e 16


bits é o suficiente para codificar a maioria delas nas instruções. No entanto,
algumas vezes elas são grandes.

O conjunto de instruções MIPS inclui a instrução lui, “load upper


immediate” (carregue na parte alta o imediato), que carrega um valor
imediato de 16 bits nos 16 bits de mais alta ordem de um registrador
especificado e zera os 16 bits de mais baixa ordem:

lui $8, 255

nome op rs rt immediate
No. bits 6 5 5 16
decimal 15 0 8 255

aula12.doc 2 de 6
Uma instrução or (ou lógico) pode, então, ser usada junto com a lui para
compor constantes de 32 bits:

0xaaaa4444:.ascii “string”

lui $8, 0xaaaa


or $8, $8, 0x4444

3.5.3 Endereçamento Relativo ao PC


As instruções de desvio condicional da ISA MIPS usam uma constante como
operando que indica qual é o endereço de desvio. Exemplo:

bne $8, $21, 100

nome op rs rt immediate
No. bits 6 5 5 16
decimal 5 8 21 100

Uma constante de 16 bits não permite acessar todas as instruções da


memória, que é endereçada com 32 bits. No entanto, na maioria dos casos,
as instruções de desvio condicional saltam para instruções próximas a ela na
memória.

Por essa razão, a constante que indica o endereço de desvio indica, na


verdade, qual é a distância ao alvo do desvio relativa à posição da instrução
de desvio na memória.

Para saber qual instrução deve ser trazida da memória para ser executada, o
processador usa o registrador PC (“Program Counter” – contador de
programa).

Quando uma instrução de desvio condicional é trazida da memória, o


registrador PC contém o endereço dela. Assim, quando um desvio é tomado,
o processador apenas soma a constante (valor imediato) ao PC e coloca o
resultado em PC. O que faz com que o fluxo de execução seja desviado para
o novo endereço em PC.

aula12.doc 3 de 6
Para tornar o hardware mais simples e poderoso, a constante indica, na
verdade, a distância relativa à instrução imediatamente após o desvio e a
distância é indicada em instruções (words, ou 4 bytes) a partir da instrução
seguinte.

Diferente das instruções de desvio condicional, as instruções jal e j não


somam sua constante (valor imediato) ao PC, mas substituem 26 bits de PC
por ela – os 4 bits de mais alta ordem e os dois de mais baixa ordem (que são
sempre iguais a zero no PC porque as instruções são de 32 bits) não são
alterados.

j 10000

nome op Address
No. bits 6 26
decimal 2 10000

Se os 4 bits de mais alta ordem de PC forem iguais a zero, esta operação é


equivalente a saltar para o endereço igual a quatro vezes o valor da
constante. Este esquema é simples de implementar em hardware (não é
necessário fazer a soma). As complexidades que ele traz são tratadas pelo
assembler (montador).

Exemplo:
label endereço instrução
loop: 1000 add $t0, $t0, 1
1004 beq $t0, $t1, Exit (1) # PC = PC + 4 + Exit (1) * 4 =
# 1012
1008 j loop (250) # PC = loop * 4 = 1000 (os dois
# bits de mais alta ordem de PC
# são iguais a zero)
Exit: 1012

aula12.doc 4 de 6
3.5.3 Endereçamento Base mais Deslocamento
Este tipo de endereçamento é utilizado pelas instruções load/store:

lw $8, 1200($19)

Nome op rs rt address
No. bits 6 5 5 16
Decimal 35 19 8 1200

sw $8, 1200($19)

nome op rs rt Address
No. bits 6 5 5 16
decimal 43 19 8 1200

O segundo operando (o primeiro é o registrador $8), que está na memória, é


acessado no endereço:

(1200+$19)

que é igual ao conteúdo do registrador base, $19, mais a constante de


deslocamento 1200.

aula12.doc 5 de 6
Sumário:

Existem outras formas de endereçamento presentes em outras ISAs, mas não


na ISA MIPS original.

aula12.doc 6 de 6
3.6 Arrays versus Pointers
Muitos programadores iniciantes acham o conceito de “pointer” difícil de
entender. Contudo, um exemplo em assembly pode tornar o entendimento
deste conceito mais fácil. O trecho de código em C abaixo limpa (zera) o
conteúdo de um array.

void
clear_array (char array[], int size)
{
int i;

for (i = 0; i < size; i = i + 1)


array[i] = 0; /* o elemento i de array recebe zero */
}

O trecho abaixo faz a mesma coisa usando pointers.

void
clear_array_p (char *array, int size)
{
char *p;

for (p = array; p < array+size; p = p + 1)


*p = 0; /* a posição de memória apontada por p recebe zero */
}

O trecho em assembly abaixo implementa a primeira versão. Os


registradores $4 e $5 são usados para passar os parâmetros para a função.

clear_array: move $2, $0 #i=0


loop: add $14, $2, $4 # $4 = endereço do array; $14 aponta para
# array[i]
sb $0, 0($14) # array[i] = 0
addi $2, $2, 1 #i=i+1
blt $2, $5, loop # $5 = size; se i < size vai para loop
jr $31

aula13.doc 1 de 2
O trecho em assembly abaixo implementa a versão usando pointers.

clear_array_p: move $2, $4 # p = array


add $14, $4, $5 # $14 = endereço do array + tamanho
# do array ($5)
loop: sb $0, 0($2) # *p = 0; o mesmo que array[i] = 0
add $2, $2, 1 #p=p+1
blt $2, $14, loop # se p < array+size vai para loop
jr $31

A versão com pointers é mais rápida (apenas três instruções no loop) porque
a variável i não precisa ser mantida – o pointer p aponta diretamente para o
item a ser alterado.

aula13.doc 2 de 2
4. Montadores, Ligadores, Carregadores, Compiladores e
Interpretadores

4.1 Montadores
Os montadores, ou “assemblers”, montam um programa em linguagem de
máquina a partir de sua versão em linguagem de montagem, ou
linguagem “assembly”. Eles:
1. Acham um endereço inicial para o programa
2. Convertem pseudo-instruções para o conjunto de instruções
equivalente
3. Convertem macros no conjunto de instruções e dados equivalentes
4. Transformam cada parte dos comandos em linguagem assembly em
opcode, número de registrador, constante, etc.
5. Escrevem o programa em linguagem de máquina em um arquivo
com as instruções ordenadas e com os endereços indicados por
elas, especificados como labels, já convertidos para números
quando possível

O arquivo gerado pelo montador é chamado de arquivo objeto e, em


geral, não pode ser executado diretamente pela máquina por conter
referências a sub-rotinas e dados especificados em outros arquivos. Um
arquivo objeto típico para o sistema operacional Unix contém seis seções
distintas:
1. header – descreve as posições e os tamanhos das outras seções
2. text segment – contém o código em linguagem de máquina
3. data segment – contém os dados especificados no arquivo fonte
4. relocation information – lista as instruções e palavras de dados que
dependem de endereços absolutos
5. symbol table – associa endereços com símbolos especificados no
arquivo fonte e lista os símbolos não resolvidos (que estão ligados
a outros arquivos)
6. debugging information – contém uma descrição sucinta de como o
arquivo foi compilado, de modo a permitir que um programa
debugger possa encontrar instruções e endereços indicados no
arquivo fonte

aula14.doc 1 de 5
Formato de um header de arquivo a.out antigo:
struct _exec_header
{
unsigned char dynToolVer;
unsigned char machineType;
unsigned short int magicNumber;
unsigned int textSize;
unsigned int dataSize; /* initialized global and static variables */
unsigned int bssSize; /* noninitialized global and static variables */
unsigned int symbolTabSize;
unsigned int entryPoint;
unsigned int textRelSize;
unsigned int dataRelSize;
};

O header é a primeira seção do arquivo a.out. As demais seções são


escritas em sequencia no a.out.

Para gerar um programa executável a partir de um ou mais arquivos


objeto temos que usar um linker.

aula14.doc 2 de 5
4.2 Ligadores
Muitas vezes, um programa pode ser decomposto em várias rotinas, cada
uma responsável por aspectos específicos da computação a ser executada
pelo programa.

Por exemplo: um programa para computar o total da folha de pagamento


de uma empresa pode ser divido em uma rotina que calcule a salário de
cada funcionário, levando em consideração o fundo de garantia,
contribuição ao INSS, etc, e uma outra que compute o total a ser
despendido para o pagamento de todos os funcionários.

float
calcula_um_salário (float salário_base)
{
float salário_total;

salário_total = salário_base + salário_base * FATOR_FGTS +


salário_base * FATOR_INSS;
return (salário_total);
}

float
salário_total (float salário_base[], int numero_funcionários)
{
int i;
float total;

total = 0.0;
for (i = 0; i < numero_funcionários; i++)
total = total + calcula_um_salário (salário_base [i]);

return (total);
}

Estas rotinas podem estar em arquivos separados. Na verdade, programas


grandes são, em geral, divididos em diversas rotinas que são, por sua vez,
agrupadas em diversos arquivos.

Ligadores, ou “likers”, são programas especiais que recebem como


entrada os arquivos objeto correspondentes a estes arquivos e geram
como saída o programa final em linguagem de máquina.

aula14.doc 3 de 5
Um linker realiza, então, quatro tarefas básicas:
1. Determina as posições de memória para os trechos de código de
cada módulo que compõe o programa sendo “linkado”
2. Resolve as referências entre os arquivos
3. Procura nas bibliotecas (libraries), indicadas pelo programador, as
rotinas usadas nos fontes de cada módulo
4. Indica ao programador quais são os labels que não foram
resolvidos (não tenham correspondente em nenhum módulo ou
library indicados)

Assim como os arquivos objeto, os programas executáveis gerados por


linkers são divididos em seções. Na verdade, as seções são tipicamente as
mesmas presentes nos arquivos objeto, exceto que, neste caso, não há
símbolos não resolvidos.

4.3 Carregadores
Programas que tenham sido linkados sem erros podem ser executados.
Para executar um programa, um programa carregador, ou loader, é
utilizado. O loader é, em geral, parte do sistema operacional.

Para iniciar a execução de um programa, o sistema operacional (loader)


realiza as seguintes operações:
1. Lê o header do executável para determinar o tamanho das suas
diversas partes
2. Separa um trecho da memória para receber os segmentos text, data,
e também para acomodar o stack
3. Copia as instruções e os dados para o trecho de memória separado
4. Copia os argumentos passados ao programa para a área de memória
separada para stack
5. Inicializa os registradores do processador para valores apropriados
(como um exemplo pode-se citar o que guarda o endereço do topo
do stack)
6. Salta para a primeira instrução do programa usando a instrução da
máquina utilizada para chamada de procedimento (jal, no caso da
MIPS ISA)

4.4 Compiladores
Compiladores são programas que recebem como entrada arquivos texto
contendo módulos escritos em linguagem de alto nível e geram como
saída arquivos objeto correspondentes a cada módulo, ou, se todas as
bibliotecas e módulos são apresentados como entrada, geram um
programa executável diretamente.

aula14.doc 4 de 5
4.5 Interpretadores
Programas interpretadores recebem como entrada arquivos texto
contendo programas em linguagem assembly ou linguagem de alto nível,
ou arquivos binários com instruções de máquina, e os executam
diretamente.

Interpretadores percorrem os programas, a partir de seu ponto de entrada,


executando cada comando.

Algumas vezes, antes de poderem ser interpretados, os programas têm


que ser pré-processados. No caso de Java, por exemplo, o programa fonte
é transformado em byte-code, em um processo equivalente ao processo
de compilação, e posteriormente pode ser interpretado (executado) por
uma máquina virtual Java.

Na verdade, os processadores são interpretadores implementados em


hardware!

aula14.doc 5 de 5
5. Aritmética de Computador
5.1 Números negativos
Números podem ser representados em qualquer base. Nós, humanos,
preferimos usar a base 10, talvez porque tenhamos 5 dedos em cada mão.

Existem várias vantagens em usar números binários em computadores


(como vimos anteriormente); assim, computadores usam números
binários e toda a sua aritmética é baseada nisso.

Para nós, humanos, números não têm, necessariamente, um limite de


tamanho. Computadores, por razões de custo e dificuldade de
implementação, têm limites no tamanho dos números que podem ser
representados em suas operações básicas.

Na MIPS ISA o tamanho básico dos números inteiros é 32 bits. Com 32


bits um processador MIPS pode representar o zero e números positivos
que vão de 1 a 232 – 1 (4.294.967.295). Mas como representar números
negativos?

O ideal seria ter um número igual de números negativos e positivos.


Contudo, temos também que representar o zero.

Como 232 é um número par, optou-se por termos 2 31 – 1 números


positivos, o zero, e 231 números negativos, com um número negativo a
mais que o total de números positivos.

Esta regra também vale quando o computador tem menos bits para
representar números inteiros. Assim, temos:

Com 32 bits:
base 2 (binário) base 10 (decimal)
0000 0000 0000 0000  0000 0000 0000 0000 = 0
0000 0000 0000 0000  0000 0000 0000 0001 = 1
0000 0000 0000 0000  0000 0000 0000 0010 = 2
...
0111 1111 1111 1111  1111 1111 1111 1101 = 2.147.483.645
0111 1111 1111 1111  1111 1111 1111 1110 = 2.147.483.646
0111 1111 1111 1111  1111 1111 1111 1111 = 2.147.483.647
1000 0000 0000 0000  0000 0000 0000 0000 = ­2.147.483.648
1000 0000 0000 0000  0000 0000 0000 0001 = ­2.147.483.647
1000 0000 0000 0000  0000 0000 0000 0010 = ­2.147.483.646
1000 0000 0000 0000  0000 0000 0000 0011 = ­2.147.483.645
...
1111 1111 1111 1111  1111 1111 1111 1110 = ­2

aula15.doc 1 de 4
1111 1111 1111 1111  1111 1111 1111 1111 = ­1

aula15.doc 2 de 4
Com 4 bits:
base 2 (binário) base 10 (decimal)
0000  = 0
0001  = 1
0010  = 2
0011  = 3
0100  = 4
0101  = 5
0110  = 6
0111  = 7
1000  = ­8
1001  = ­7
1010  = ­6
1011  = ­5
1100  = ­4
1101  = ­3
1110  = ­2
1111  = ­1

Em alguns casos, números negativos não são úteis ou não faz sentido
utiliza-los. Este é o caso de endereços de memória, por exemplo. Não faz
sentido usar endereços de memória negativos.

Por esta razão, muitas instruções aritméticas operam em dois modos:


signed (com sinal) ou unsigned (sem sinal).

O modo default (definido a priori) é signed; quando queremos que a


instrução opere sem sinal (considerando todos os números como
positivos) temos que especificar. Por exemplo:

slt $8, $16, $17 # comparação com sinal (signed)


sltu $9, $16, $17 # comparação sem sinal (unsigned)

Exercício:
Suponha que $16 contenha o número binário:
1111 1111 1111 1111  1111 1111 1111 1111 

e que $17 contenha o número binário:


0000 0000 0000 0000  0000 0000 0000 0001 

Quais são os valores de $8 e $9 após a execução das instruções abaixo?


slt $8, $16, $17
sltu $9, $16, $17

aula15.doc 3 de 4
5.2 Soma e Subtração
Para somar números binários podemos usar a mesma técnica que usamos
quando somamos números decimais: somar dois dígitos de cada vez.
Exemplo:

decimal binário (8 bits)


 18   0001 0010
+25 =  +0001 1001 =
 ­­   ­­­­­­­­­
 43   0010 1011

A subtração também usa a mesma técnica:

decimal binário ou, somando com o -18


 25  0001 1001   0001 1001
­18 = ­0001 0010 = +1110 1110 =
 ­­  ­­­­­­­­­  ­­­­­­­­­
 07  0000 0111  0000 0111

Em hardware, a subtração é normalmente implementada como a soma


com o complemento de dois do subtraendo. O complemento de dois de
um número binário é igual a seu complemento bit-a-bit mais 1. O
complemento de dois de 18 é:

18 = 0001 0010
complemento bit-a-bit = 1110 1101
complemento bit-a-bit +1 = 1110 1110

Assim, a operação 25 – 18 em binário pode, também, ser feita (e em geral


é feita) como abaixo:
 0001 1001    
+1110 1101 complemento (complemento de 1) de 18
+        1 =
 ­­­­­­­­­
 0000 0111

aula15.doc 4 de 4
Um problema ocorre quando os números somados produzem um
resultado com mais bits do que o que pode ser representado.

Quando isso ocorre, diz-se que houve um overflow (“transbordo”). Pode


acontecer overflow em somas e em subtrações. A tabela abaixo indica em
quais situações podemos afirmar que ocorreu um overflow.

Operação Operando A Operando B Resultado


A+ B 0 0 <0
A+ B <0 <0 0
A–B 0 <0 <0
A–B <0 0 0
(o zero sozinho indica números positivos ou o número zero)

Quando ocorre um overflow, o processador deve indicar ao programador.


Processadores usam exceções (ou interrupções) para indicar eventos
como um overflow.

Para evitar a sinalização de um overflow quando ele não faz sentido,


instruções que desconsideram sinais são utilizadas:

 add (soma com sinal) e addi (soma imediato com sinal), podem
causar exceções quando o processador é configurado para tal
 addu (soma sem sinal), addui (soma imediato sem sinal), não
causam exceções

5.3 Operações Lógicas


Operações lógicas, tais como E, OU, OU EXCLUSIVO e NEGAÇÃO,
são feitas bit a bit. Exemplo:

binário (8 bits)
0001 0010
&0001 1001 =
--------------
0001 0000

Outro tipo de operação lógica sempre presente em computadores é o shift


(deslocamento):

para direita: 0010 1011 >> 1 = 0001 0101

para esquerda: 0010 1011 << 1 = 0101 0110

aula15.doc 5 de 4
5.4 Unidade Aritmética e Lógica (Arithmetic and Logic Unit – ALU)
A ALU é a parte central do hardware de uma CPU. Ela é responsável por
realizar operações aritméticas e lógicas básicas, e pode ser implementada
com quatro componentes: porta E (And), porta OU (Or), inversor
(Inverter) e multiplexador (multiplexer).

a b c=a.b
0 0 0
E (AND) a
b c 0 1 0
1 0 0
1 1 1

a b c=a+b
0 0 0
OU (OR) a
b
c 0 1 1
1 0 1
1 1 1

a c=a
Inversor a c 0 1
1 0
d

d c=
Multiplexador a 0
c 0 a
b 1
1 b

Uma ALU de um bit para as operações E e OU pode ser implementada


como abaixo:
Operação

a
Resultado
b

Podemos implementar um somador de 1 bit usando os componentes


básicos. Para isso, iniciamos com a “tabela verdade” do somador de um
bit.

aula16.doc 1 de 6
Entradas Saídas
a b “vem um” “vai um” soma
0 0 0 0 0
0 0 1 0 1
0 1 0 0 1
0 1 1 1 0
1 0 0 0 1
1 0 1 1 0
1 1 0 1 0
1 1 1 1 1

A saída “vai um” é igual a 1 quando:

vai_um = a.b.vem_um + a.b.vem_um + a.b.vem_um + a.b.vem_um (5.1)

onde “.” representa um E lógico, “+” representa um OU e entradas


sublinhadas indicam um termo verdadeiro quando a entrada é igual a
zero.

A equação 5.1 pode ser simplificada para:

vai_um = b.vem_um + a.vem_um + a.b (5.2)

já que (a.b.x + a.b.x) = (a.b). O circuito equivalente à equação 5.2 é como


abaixo:

vem um

vai um

Exercício: Faça a equação e o circuito que produz o bit de soma.

aula16.doc 2 de 6
Uma ALU de 1 bit para a soma, o E e o OU lógicos pode ser
implementada como abaixo:
Operação

vem um

a
0

1 Resultado
b
2

vai um

Uma ALU de 32 bits:

vem um Operação

a0 vem um

ALU0 resultado0
b0 vai um

a1 vem um

ALU1 resultado1
b1 vai um

.
.
.

a31 vem um

ALU31 resultado31
b31 vai um

aula16.doc 3 de 6
Para implementar a subtração, podemos mudar nossa ALU de um bit
básica para permitir a inversão da entrada b e a soma de 1 através do
“vem um”:
b_invertido Operação
vem um

a 0

b 0 1 Resultado
1 2

vai um

Mudanças específicas no bit de mais alta ordem permitem incluir o teste


de “overflow”.

Para incluir a operação feita pela instrução slt (“set on less than”), temos
que adicionar uma entrada “less” no multiplexador de resultado.

Esta entrada é conectada a zero nas ALUs dos bits 1 até 31 (ALU[1-31]).
A ALU do bit de mais alta ordem (ALU31) é modificada para gerar o
sinal “set”, que será igual a 1 se a < b. O sinal set é conectado à entrada
less do bit de mais baixa ordem (ALU0).

b_invertido Operação
vem um

a 0

b 0 1 Resultado
1 2

+
less 3
set

Detecção de
overflow
overflow

vai um

aula16.doc 4 de 6
As instruções de desvio, beq e bne, podem também usar a ALU para
saber se dois registradores são iguais ou diferentes. Para isso, basta
subtrair o valor em um registrador pelo valor no outro e testar se o
resultado é igual a zero.

Até este ponto, a ALU é capaz de realizar a subtração, mas não a


comparação para ver se o resultado é igual a zero. Para implementar a
comparação, basta incluir uma porta OU na saída da ALU e um inversor
na saída desta:

vem um Operação

a0 vem um

ALU0 resultado0
less
b0 vai um

a1 vem um

ALU0 resultado1
0 less
b1 vai um

.
. .
.
. zero
.

a31 vem um

ALU0 resultado31
0 less
b31 vai um
overflow
set

Outras funções de interesse, como o OU EXCLUSIVO (“exclusive or” ou


xor), podem ser incluídas na ALU.

Algumas operações não são, em geral, implementadas na ALU, por serem


muito custosas (em termos de tempo ou área de chip). Exemplos: shift,
multiplicação, divisão e operações de ponto flutuante.

aula16.doc 5 de 6
O símbolo normalmente usado para representar uma ALU é como abaixo:

a b Controle Operação feita pela ALU


000 E
controle vai um
001 OU
ALU
010 soma
zero overflow 110 subtração
111 set-on-less-than
resultado … …

aula16.doc 6 de 6
5.4 Multiplicação Inteira
Podemos realizar a operação de multiplicação de números decimais como
abaixo:

1000 multiplicando
x 1001 multiplicador
------
1000
0000
0000
1000
-----------
1001000 produto

Um fato importante mostrado pelo exemplo acima é que o produto tem


muito mais casas decimais que o multiplicando e o multiplicador. Na
verdade, se o multiplicando tem m casa e o multiplicador tem n casas, o
produto tem m+ n casas.

Se os números acima são binários, a operação de multiplicação pode ser


feita da mesma maneira e o resultado teria os mesmos dígitos (1000 = 8,
1001 = 9; 1000x1001 = 1001000 = 72).

Na verdade, a multiplicação binária consiste, neste caso, apenas em


copiar o multiplicando deslocado do número apropriado de casas ou
copiar zero deslocado do número apropriado de casas.

Para implementar a multiplicação via hardware existem várias


possibilidades. Vamos analisar várias delas. Mas antes, precisamos
compreender como funcionam os flip-flops.

Flip-Flops
Flip-flops são dispositivos eletrônicos capazes de guardar estado. O mais
simples é o S-R:

aula17.doc 1 de 11
Um tipo muito importante de flip-flop é o flip-flop tipo D. Existem dois
tipos: acionado por nível e acionado pela borda. Abaixo o acionado por
nível:

E o abaixo é o acionado pela borda:

aula17.doc 2 de 11
Podemos agrupar flip-flops acionados pela borda ou por nível de modo a
implementar registradores como os de um processador MIPS.

aula17.doc 3 de 11
Podemos agrupar registradores do tipo D acionados pela borda de modo a
implementar um registrador de deslocamento:

Primeiro hardware e algoritmo para a multiplicação


A figura abaixo mostra o nosso primeiro hardware para multiplicação.

Vamos assumir que o multiplicador esta no registrador multiplier


(multiplicador em inglês), que o multiplicando esta no registrador
multiplicand e que o registrador com o produto está inicializado com
zero.

O hardware da figura acima testa o bit de mais baixa ordem do


multiplicador e, se seu valor é 1, soma o valor do multiplicando com o
valor no registrador produto e guarda o resultado no registrador produto;
caso contrário, o hardware não altera o valor do registrador produto.

aula17.doc 4 de 11
Além disso, o hardware desloca o conteúdo do registrador multiplicador
de um bit para a direita e o valor do registrador multiplicando de um bit
para a esquerda. Este processo é repetido até que o registrador
multiplicador tenha sido deslocado de 32 bits.

A figura abaixo apresenta o funcionamento deste hardware de


multiplicação na forma algorítmica.

aula17.doc 5 de 11
Exemplo de funcionamento:

11

Se cada passo na tabela acima necessitasse de um ciclo de clock, este


algoritmo levaria quase 100 ciclos de clock para fazer esta multiplicação.

Operações de adição e subtração ocorrem com uma freqüência de 5 a 100


vezes maior que a multiplicação; assim, uma diferença de 100 vezes no
tempo destas operações pode não afetar fortemente o desempenho em
muitos programas. Contudo, já vimos que a lei de Amdahl mostra que
operações lentas que ocorrem com frequência moderada podem afetar
significativamente o desempenho do computador.

aula17.doc 6 de 11
Segundo hardware e algoritmo para a multiplicação
Pioneiros da computação observaram que metade dos bits do
multiplicando do nosso hardware anterior era sempre igual a zero. Assim,
uma ALU de 64 bits era um desperdício.

A figura abaixo mostra o nosso segundo hardware para multiplicação.


Nele, o produto é deslocado para a direita e o multiplicando não precisa,
então, ser deslocado.

aula17.doc 7 de 11
A figura abaixo mostra o algoritmo implementado por este hardware.

aula17.doc 8 de 11
Veja um exemplo da operação do nosso segundo hardware de
multiplicação inteira:

aula17.doc 9 de 11
Terceiro hardware e algoritmo para a multiplicação
Os pioneiros da computação logo observaram que, no último hardware
apresentado, metade dos bits do produto era jogada fora no processo de
multiplicação, ao mesmo tempo em que um número de bits igual era
jogado fora do multiplicador. Assim, eles criaram o nosso terceiro
hardware, que é como abaixo.

Neste hardware, os 32 bits de baixa ordem do produto são carregados


inicialmente com o valor do multiplicador, ao passo que seus 32 bits de
mais baixa ordem são inicialmente zerados. No final do processo, o
registrador produto contém o resultado da multiplicação.

aula17.doc 10 de 11
A figura abaixo mostra o algoritmo implementado por nosso hardware
final.

Multiplicação com sinal


Até agora nós apenas estudamos multiplicação sem sinal. A maneira mais
simples de implementar a multiplicação com sinal é separar os bits de
sinal (fazer a multiplicação de 31 bits, apenas) e, no final da
multiplicação, colocar o sinal no produto usando a regra que aprendemos
no primário: o sinal do produto é negativo quando os sinais do
multiplicando e do multiplicador são diferentes.

aula17.doc 11 de 11
5.5 Divisão Inteira
A divisão é o recíproco da multiplicação; ela é menos freqüente nos
programas e é mais complexa de se implementar em hardware.

Seguindo o exemplo que usamos para multiplicação, vamos começar a


estudar a divisão binária relembrando nossos conhecimentos sobre
divisão decimal. Podemos dividir 1.001.010 por 1000 como abaixo:

dividendo divisor

  1001010  1000
­ 1000 1001 quociente
  0001
     10
     101
     1010
   ­ 1000
 0010 resto

Os dois operandos, dividendo (dividend) e divisor (divisor), produzem


dois resultados na divisão: quociente (quotient) e resto (remainder). A
relação entre os operandos e os resultados da divisão pode também ser
expressa como:

dividendo = quociente x divisor + resto

onde o resto é menor que o divisor. Algumas vezes, programas usam a


operação de divisão apenas para obter o resto.

Como no caso da multiplicação, para implementar a divisão em hardware


existem várias possibilidades. Vamos analisar várias delas.

aula18.doc 1 de 16
Primeiro hardware e algoritmo para a divisão
A figura abaixo mostra o nosso primeiro hardware para divisão.

Este hardware imita o algoritmo que usamos para a divisão decimal. Para
iniciar uma divisão, zeramos o registrador de quocient, carregamos os 32
bits de mais alta ordem do registrador divisor com o divisor e o
registrador remaider com o dividendo.

aula18.doc 2 de 16
A figura abaixo apresenta o funcionamento deste hardware de divisão na
forma algorítmica.

O hardware do computador não é experto como um ser humano para ver


que o divisor é menor que o dividendo. Por isso, ele primeiro subtrai o
divisor do dividendo e, se o resultado for negativo, ele restaura o valor do
dividendo somando o divisor com o dividendo novamente. O resultado da
divisão estará nos registradores quotient e remaider no fim de 33 passos.

aula18.doc 3 de 16
Exemplo de funcionamento do algoritmo:

00

Como pode ser observado acima, precisamos de 5 iterações do algoritmo


para fazer uma divisão de 4 bits.

aula18.doc 4 de 16
Segundo hardware e algoritmo para a divisão
Como no caso da multiplicação, os pioneiros da computação observaram
que metade dos bits do divisor do nosso hardware anterior era sempre
igual a zero. Assim, uma ALU de 64 bits era um desperdício.

A figura abaixo mostra o nosso segundo hardware para divisão. Nele, o


resto é deslocado para a esquerda e o divisor não precisa, então, ser
deslocado. O alinhamento da entrada esquerda e da saída da ALU com
relação ao registrador remainder garantem o correto funcionamento deste
novo hardware. Para iniciar uma divisão, zeramos o registrador quocient,
carregamos o registrador divisor com o divisor e o registrador remaider
com o dividendo.

A segunda melhora proporcionada pelo hardware acima vem do fato de


que o primeiro passo de sua operação nunca produz um 1 no quociente:
como a divisão por zero não é uma operação válida, o valor inicial do
divisor será sempre maior que o conteúdo da parte alta do remainder, que
inicialmente é igual a zero. Através da inversão da ordem das operações
para (i) deslocar o remainder e então (ii) fazer a primeira subtração, um
passo do algoritmo pode ser eliminado.

aula18.doc 5 de 16
A figura abaixo mostra o algoritmo implementado pelo segundo hardware
de divisão.

aula18.doc 6 de 16
Veja agora um exemplo da operação do nosso segundo hardware de
divisão inteira:

aula18.doc 7 de 16
Terceiro hardware e algoritmo para a divisão
Como no caso da multiplicação, os pioneiros da computação observaram
que, no último hardware apresentado, a metade dos bits do registrador
remainder era jogada fora no processo de divisão, ao mesmo tempo em
que um número igual de bits era jogado fora do registrador quotient.
Assim, eles criaram o nosso terceiro hardware, que é como abaixo.

Neste hardware, os 32 bits de baixa ordem do remainder são carregados


inicialmente com o valor do dividendo, ao passo que seus 32 bits de mais
alta ordem são inicialmente zerados. A cada passo da divisão, um bit do
quociente é inserido no registrador; o dividendo é deslocado em conjunto.

Uma conseqüência de juntar os dois registradores e usar a nova ordem


das operações introduzida no algoritmo anterior é o deslocamento do
resto de um passo a mais que o necessário. Para corrigir isso, no final do
processo, a parte alta do registrador remainder é deslocada de um bit para
a direita. O registrador remainder conterá, após a divisão, o resto na parte
alta e o quociente na parte baixa.

aula18.doc 8 de 16
A figura abaixo mostra o algoritmo implementado por nosso hardware
final.

aula18.doc 9 de 16
Veja abaixo um exemplo de divisão usando este último
hardware/algoritmo.

1110

Divisão com sinal


Até agora nós apenas estudamos a divisão sem sinal. A maneira mais
simples de implementar a divisão com sinal é separar os bits de sinal
(fazer a divisão de 31 bits, apenas) e, no final da divisão, colocar o sinal
no quociente usando a regra que aprendemos no primário: o sinal da
divisão é negativo quando os sinais do divisor e do dividendo são
diferentes. Um complicador é que temos que colocar o sinal do resto
corretamente também. Para isso podemos usar a equação:

dividendo = quociente x divisor + resto

Esta equação esta sempre correta. Experimentando com ela é possível


verificar que o sinal do resto é sempre igual ao sinal do dividendo. Assim,
a divisão com sinal torna o sinal do quociente negativo se os sinais do
divisor e do dividendo forem diferentes e torna o sinal do resto sempre
igual ao sinal do dividendo.

O mesmo hardware para a divisão pode ser usado para a multiplicação.


Para isso, é necessário que o registrador de 64 bits possa ser deslocado
para a direita e para a esquerda e que a ALU possa somar e subtrair.
Processadores MIPS possuem, além dos 32 registradores para operações
inteiras, dois registradores para apoiar a multiplicação e a divisão: Hi e
Lo. A tabela a seguir mostra como usar as instruções de multiplicação,
divisão e de leitura de Hi e Lo.

aula18.doc 10 de 16
/
/

O assembler da MIPS ISA permite que se utilize pseudo-instruções para


multiplicação e divisão com três operandos. Neste caso, o assembler usa
as instruções mflo e mfhi para copiar os registradores Hi e Lo para os
registradores apropriados.

A divisão por zero produz infinito como resultado. Esta é uma operação
ilegal em qualquer computador. Muitas máquinas detectam divisão por
zero em hardware, gerando uma exceção.

aula18.doc 11 de 16
5.5 Números de Ponto Flutuante
Além dos números inteiros, com e sem sinal, linguagens de programação
de alto-nível incluem facilidades para representar os números reais da
matemática. Veja alguns exemplos de números reais:

3.141592… (pi)
2.71828… (e)
0.000000001 ou 1.0 x 10-9
3,155,760,000 ou 3.15576 x 109

A notação usada nos dois últimos números acima é chamada de notação


científica. Um número em notação científica com apenas um número
diferente de zero à esquerda do ponto decimal é dito normalizado. Assim,
1.0 x 10-9 está normalizado, enquanto que 10 x 10-8 e 0.1 x 10-10 não estão.

Nós também podemos escrever números binários em notação científica:

1.0 x 2-1

A aritmética computacional que suporta números binários no formato


acima é conhecida como aritmética de ponto flutuante (floating point)
porque ela representa números onde o ponto decimal não está fixo: nos
números inteiros o ponto decimal está fixo logo após o último algarismo à
direita e não há números diferentes de zero à direita do ponto.

Na linguagem C, variáveis do tipo ponto flutuante são declaradas como


abaixo:

int i; /* i é uma variável do tipo inteiro */


float f; /* f é uma variável do tipo ponto flutuante */

A forma binária de números em notação científica é como abaixo:

1.xxxxxx x 2yyyy

onde xxxxxx e yyyy são números binários.

aula18.doc 12 de 16
Os números de ponto flutuante da MIPS ISA podem ser representados em
dois formatos distintos, que são parte do IEEE 754 floating-point
standard (Standard 754 do Instituto dos Engenheiros Eletricistas e
Eletrônicos dos Estados Unidos):

O bit S é o sinal, o campo exponent é o expoente e mantissa são os bits de


mantissa, ou bits significantes do número.

Em precisão simples podemos representar números na faixa que vai de


(na base 10) 2.0 x 1038 a 2.0 x 10-38, enquanto que, em precisão dupla,
podemos representar números na faixa que vai de (na base 10) 2.0 x 10 308
a 2.0 x 10-308, aproximadamente.

É possível que ocorra um overflow como resultado de uma operação de


ponto flutuante. Um overflow em ponto flutuante ocorre quando o
número de bits do expoente do resultado é maior que o número de bits
possível no formato em que a operação é realizada (precisão simples ou
dupla).

Em certos casos, o resultado de uma operação pode ser um número


pequeno demais para ser representado (o número de bits do expoente
negativo é grande demais). Este evento é conhecido como underflow.

Para comprimir mais bits na parte significante dos números de ponto


flutuante, o padrão IEEE 754 torna implícito o 1 antes do ponto dos
números binários normalizados.

Assim, o campo significand dos números de precisão simples tem, na


verdade, 24 bits, enquanto que o dos números de precisão dupla tem 53
bits.

aula18.doc 13 de 16
Uma vez que o número zero não tem um 1 antes do ponto, ele é
representado com o expoente reservado, zero (ver mais detalhes abaixo).

Se numerarmos os bits do significand da esquerda para direita como s1,


s2, …, os números restantes são representados como abaixo:

(-1)S x (1 + (s1 x 2-1) + (s2 x 2-2) + (s3 x 2-3) + …) x 2E


Os projetistas do padrão IEEE 754 queriam, também, que números na
representação ponto flutuante pudessem ser facilmente manipulados por
operações inteiras.

Por isso que o bit de sinal é o bit de mais alta ordem, o que permite fazer
o teste de maior ou menor que zero facilmente, usando as mesmas
instruções para este teste em inteiros.

O teste de zero é igual para números inteiros e de ponto flutuante, já que


o zero tem a mesma representação para os dois tipos de número.

Colocar o expoente antes do significante simplifica a ordenação de


números de ponto flutuante, já que números normalizados com expoentes
maiores aparentam de fato ser maiores se são lidos como inteiros e tem o
mesmo sinal.

Números com expoentes negativos não poderiam ser ordenados como


inteiros se o padrão IEEE 754 usasse a mesma notação que aprendemos
para números negativos para representar expoentes negativos.

A notação mais desejável, neste caso, representaria o expoente mais


negativo como 000…00 e o expoente mais positivo como 111…11. Uma
notação deste tipo é chamada de biased notation (notação deslocada).

O IEEE 754 usa um deslocamento de 127 para precisão simples. Assim,


–1 é representado pelo padrão de bits –1+127, ou 126 = 0111 1110, e +1 é
representado por 1+127, ou 128 = 1000 0000.

Isso significa que, segundo o IEEE 754, devemos aplicar a formula


abaixo para saber o valor de um número expresso no formato de ponto
flutuante:

(-1)S x (1 + significand) x 2(exponent – bias)

O valor do deslocamento (bias) para o expoente na precisão simples é


igual a 127 e para o expoente na precisão dupla é igual a 1023.

aula18.doc 14 de 16
Como mencionado anteriormente o expoente igual a zero é usado para
representar o zero.

Na verdade, combinações de valores reservados do expoente com valores


específicos da mantissa são usadas para representar também o overflow, o
underflow, +∞, -∞ e NaN (Not a Number).

Existem três tipos de operações que podem retornar NaN:

 Operações que produzem um número indeterminado:


o As divisões 0/0 e ± ∞ / ± ∞
o As multiplicações 0 × ± ∞ e ± ∞ × 0
o As adições ∞ + (- ∞), (- ∞) + ∞ e subtrações equivalentes
o O padrão IEEE 754 define funções específicas para calcular
potências:
 A função powr define 00, 1∞ e ∞0 como NaN.
 Mas note que as funções pow e pown definem as três
formas acima como 1.0.
 Operações reais com resultados complexos, por exemplo:
o A raiz quadrada de um número negativo.
o O logaritmo de um número negativo
o O arco seno ou coseno de um número que é menor do que -1
ou maior do que 1.
 Operações com um NaN como pelo menos um de seus operandos.

Codificações de valores especiais no padrão IEEE 754:

Exercício: Mostre a representação binária, no padrão IEEE 754, do


número –0.75 em precisão simples e dupla.

aula18.doc 15 de 16
Resposta:
Precisão simples
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 0 1 1 1 1 1 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

Precisão dupla
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 0 1 1 1 1 1 1 1 1 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 0 0 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

aula18.doc 16 de 16
5.7 Operações Aritméticas com números de Ponto Flutuante
Para somar dois números na base 10 em notação científica temos que:

1. alinhar os pontos decimais


2. somar os números
3. normalizar o resultado
4. arredondar o resultado para o número máximo de casas permitido

Exemplo: Some o número 9.999 x 101 com o número 1.65 x 10-1.


Apresente o resultado com quatro dígitos significativos.

1. Alinhamos o ponto decimal do número de menor expoente:

1.65 x 10-1 => 0.0165 x 101

2. Somamos

0.0165 x 101 + 9.999 x 101 = 10.0155 x 101

3. Normalizamos

10.0155 x 101 => 1.00155 x 102

4. Arredondamos

1.00155 x 102 => 1.002 x 102 - OK

aula19.doc 1 de 6
Um hardware para somar números de ponto flutuante tem que fazer as
mesmas operações com números binários. O algoritmo para soma de
números em ponto flutuante abaixo pode ser implementado em hardware.

Vamos ver um exemplo do uso do algoritmo acima.

Exemplo: Some os números 0.5 e –0.4375 em binário e apresente o


resultado com 4 bits de precisão; trunque o resultado na fase de
arredondamento.

aula19.doc 2 de 6
Primeiro, vamos converter os números para notação binária:

0.5 = 1/2 = 0.1 (binário) = 0.1 x 20 = 1.000 x 2-1


-0.4375 = -7/16 = -7/24 = -0.0111 (binário) = -1.110 x 2-2

Passo 1: Igualar os expoentes (modificar o número menor)

-1.110 x 2-2 => -0.111 x 2-1

Passo 2: Somar as partes significantes

1.000 x 2-1 + (-0.111 x 2-1) = 0.001 x 2-1

Passo 3: Normalizar o resultado

0.001 x 2-1 => 1.0 x 2-4

Passo 4: Arredondamento

1.000 x 2-4

O número final (acima) já está OK.

aula19.doc 3 de 6
O hardware abaixo pode ser utilizado para soma de números de ponto
flutuante conforme o algoritmo apresentado.

A subtração, multiplicação e a divisão em ponto flutuante seguem um


raciocínio equivalente ao da soma.

5.8 Aritmética Precisa


Diferente da aritmética inteira, que nos computadores representa
exatamente o seu equivalente matemático, a aritmética de ponto flutuante
é apenas uma aproximação de sua equivalente matemática.

Isso acontece porque, na matemática, existem infinitos Números Reais


entre 0 e 1, por exemplo. Por outro lado, na aritmética de ponto flutuante,
existe um certo número de números do ponto flutuante apenas.

aula19.doc 4 de 6
A figura abaixo apresenta as instruções de ponto flutuante da MIPS ISA.

aula19.doc 5 de 6
Esta tabela mostra o quanto cada uma das instruções estudadas até agora
é usada em dois programas reais: gcc – um compilador, e spice – um
programa para analisar o comportamento de circuitos elétricos.

aula19.doc 6 de 6
6 O Processador
6.1 Introdução

Um processador é uma máquina de estados que executa a seguinte sequência


de estados continuamente:

1. Leia da memória a instrução apontada pelo registrador Program Counter


(PC)
2. Incremente o PC (PC = PC + 1; ou PC = PC + 4 se a instrução for de 4
bytes)
3. Decodifique a instrução
4. Execute a instrução
5. Volte ao passo 1

Um gerador de pulsos é usado para indicar quando o processador deve


passar de um estado para o outro. Convencionou-se chamar este gerador de
pulsos de clock (relógio).

A frequência máxima de operação do processador, ou seja, o quão rápido ele


pode passar por estes passos, em GHz, é normalmente indicada como fator
de desempenho dos computadores. Um GHz (giga Hertz, ou bilhões de
Hertz) equivale a um bilhão de pulsos por segundo.

Uma máquina de estados é composta por dois tipos de circuitos interligados


de forma apropriada: circuitos combinacionais e circuitos capazes de guardar
estados.

Circuitos combinacionais possuem entradas e saídas, e suas saídas


dependem única e exclusivamente de suas entradas. Portas lógicas (E, OU,
etc) são exemplos de circuitos combinacionais. Na verdade, todos os
circuitos combinacionais são ou uma porta lógica, ou uma combinação de
portas lógicas.

Um circuito capaz de guardar estados retém o valor de um certo número de


bits até que um novo valor seja escrito nele. Como um exemplo de circuito
capaz de guardar estados, podemos citar o registrador PC. Na verdade, todos
os registradores e toda a memória de um computador são circuitos capazes
de guardar estados.

aula20.doc 1 de 10
A memória pode ser vista como uma caixa preta, contendo um registrador
específico para cada endereço, sendo que cada um deles pode ser lido ou
escrito independentemente. O banco de registradores de um computador é,
na verdade, uma pequena memória de acesso rápido.

Existem dois tipos básicos de registrador: acionado por nível ou acionado


por borda. Os registradores acionados por nível copiam a entrada para saída
enquanto o sinal de clock estiver alto (ou quando ele estiver baixo).
Registradores acionados por borda copiam o valor da entrada para a saída
quando ocorrer uma transição de clock de alto para baixo (ou de baixo para
alto).

A figura abaixo mostra uma máquina de estados genérica implementada com


registradores acionados por borda:

clock
Registradores
Lógica Combinacional

clock

Na figura, na primeira subida do clock o estado dos registradores é


modificado para o valor presente em suas entradas. O novo estado é
apresentado nas saídas dos registradores logo após a subida do clock. Estas
saídas são aplicadas às entradas da lógica combinacional, que opera sobre
elas e produz uma nova entrada para os registradores.

Quando da próxima subida do clock, as novas saídas da lógica


combinacional serão as novas entradas dos registradores. Assim, os estados
guardados nos registradores evoluem de acordo o estado inicial e conforme o
especificado pela lógica combinacional.

aula20.doc 2 de 10
Um computador é, na verdade, uma máquina de estados bastante complexa,
projetada para, a partir de um estado inicial, mudar seu estado interno
(memória, registradores, etc) de acordo com o especificado por uma
seqüência instruções, que são, na verdade, parte do estado inicial!

6.2 Datapath (Caminho dos Dados)


Os circuitos que compõem um processador podem ser divididos em duas
partes: o datapath e o controle. Os circuitos que formam o datapath são
responsáveis pela manipulação dos dados; a ALU faz parte do datapath, por
exemplo. Os circuitos de controle são responsáveis, como o nome diz, por
controlar o datapath.

Vamos fazer uma análise de um datapath típico, o datapath de um


processador MIPS. A figura abaixo mostra alguns componentes deste
datapath.

A memória e o PC são capazes de guardar estados, enquanto que o somador


é combinacional. A memória é usada para guardar as instruções e os dados
dos programas, PC guarda o endereço da instrução corrente e o somador
pode ser usado para incrementar o PC de modo a fazê-lo apontar para a
próxima instrução.

aula20.doc 3 de 10
Estes circuitos podem ser agrupados para compor a parte do datapath
responsável por trazer instruções da memória:

Neste datapath, a cada subida do clock o PC recebe PC + 4. A instrução, cujo


endereço é igual ao conteúdo de PC, é disponibilizada na saída da memória
de instruções (Instruction memory) logo após a atualização do conteúdo do
PC. A memória de instruções é composta por registradores (milhões deles)
acionados por nível; isto é, basta apresentar o endereço que a memória
devolve a instrução correspondente.

As instruções trazidas da memória são utilizadas por outras partes do


datapath, como veremos a seguir.

aula20.doc 4 de 10
Os componentes abaixo podem ser usados para compor a parte do datapath
responsável por implementar a maioria das instruções lógicas e aritméticas:

aula20.doc 5 de 10
Os componentes mostrados na figura anterior podem ser agrupados como
abaixo:

Na figura acima, uma parte dos bits da instrução é usada para determinar
quais registradores serão lidos e qual será escrito durante a execução da
instrução. Por exemplo, se a instrução é a

add $8, $17, $18


nome op rs rt rd shamt funct
campo 1 2 3 4 5 6
No. bits 6 5 5 5 5 6
decimal 0 17 18 8 0 32

os bits que especificam os registradores de leitura $17 e $18, campos 2 e 3


acima, são apresentados às entradas Read register 1 (Registrador de leitura
1) e Read register 2 (Registrador de leitura 2) do banco de registradores
Registers, respectivamente, enquanto que os bits que especificam o
registrador de escrita $8 (campo 4) são apresentados à entrada Write register
(Registrador de escrita).

Logo após a apresentação dos bits que especificam os registradores que


serão lidos, o banco de registradores disponibiliza o conteúdo dos
respectivos registradores nas saídas Read data 1 (Dado de leitura 1) e Read
data 2 (Dado de leitura 2).

A ALU recebe estes valores e opera sobre eles, conforme o que determina a
instrução, e disponibiliza o resultado da operação na sua saída. Esta, é levada
à entrada Write data (Dado de escrita).

aula20.doc 6 de 10
No final do ciclo de clock, o valor computado pela ALU é escrito no
registrador especificado em Write register.

Para implementar as instruções de leitura e escrita na memória, precisamos


dos componentes abaixo:

A memória de dados guarda os dados a serem manipulados pelo processador.


A unidade de extensão de sinal é usada para transformar a constante de 16
bits, que é um dado imediato das instruções de leitura e escrita na memória
(fazem parte da instrução), em um valor de 32 bits.

A operação de extensão de sinal consiste em copiar o bit de sinal da


constante de 16 bits, o bit 15, para os bits 16-31 da saída da unidade de
extensão de sinal.

Os componentes acima podem ser agrupados para implementar a parte do


datapath responsável pelas instruções de leitura e escrita na memória como
abaixo:

aula20.doc 7 de 10
Como no caso das instruções lógicas e aritméticas, parte dos bits da
instrução é usada para determinar quais registradores serão lidos e qual será
escrito durante a execução da instrução (se a instrução for de leitura na
memória).

A constante de 16 bits, que sempre aparece nas instruções de leitura e escrita


na memória, tem que ser estendida de sinal para ser somada ao registrador
que é usado para compor o endereço. Por exemplo, se a instrução é a

lw $8, 1200($19)
nome op rs rt address
campo 1 2 3 4
No. bits 6 5 5 16
decimal 35 19 8 1200

os bits que especificam o registrador de leitura $19, campos 2 acima, são


apresentados à entrada Read register 1 do banco de registradores Registers,
enquanto que os bits que especificam o registrador de escrita $8 (campo 3)
são apresentados à entrada Write register.

Logo após a apresentação dos bits que especificam o registrador $19, o


banco de registradores disponibiliza o seu conteúdo na saída Read data 1.
Ao mesmo tempo, a constante 1200 é apresentada à unidade de extensão de
sinal, que disponibiliza o valor 1200 entendido de sinal na entrada de baixo
da ALU.

aula20.doc 8 de 10
A ALU recebe o conteúdo de $19 e a constante 1200 extendida de sinal e
disponibiliza a soma destes dois valores na sua saída. Esta, é levada às
entradas Read address (Endereço de leitura) e Write address (Endereço de
escrita) da Data memory (Memória de dados).

No caso da instrução lw $8, 1200($19), no final do ciclo de clock o conteúdo


da posição de memória apontado pelo endereço 1200+($19) é escrito no
registrador especificado em Write register.

Se a instrução em questão fosse o sw $8, 1200($19) ao invés do lw, o


conteúdo do registrador $8 seria escrito no endereço 1200+($19) através da
entrada Write data (Dado de escrita) da memória.

Para implementar as instruções de desvio beq e bne podemos usar o datapath


abaixo:

aula20.doc 9 de 10
No caso da instrução
bne $8, $21, 100
nome op rs rt address
campo 1 2 3 4
No. bits 6 5 5 16
decimal 5 8 21 100

os bits que especificam os registradores de leitura $8 e $21, campos 2 e 3


acima, são apresentados às entradas Read register 1 e Read register 2 do
banco de registradores Registers, respectivamente.

Logo após a apresentação dos bits que especificam os registradores $8 e $21,


o banco de registradores disponibiliza seus conteúdos nas saídas Read data
1 e 2.

Ao mesmo tempo, a constante 100 é apresentada à unidade de extensão de


sinal, que disponibiliza o valor 100 entendido de sinal e deslocado para a
esquerda de dois bits na entrada do Adder (somador).

A constante 100 estendida de sinal é deslocada de dois, que é equivalente a


multiplica-la por quatro. Isto é feito porque as instruções sempre ocupam
endereços múltiplos de quatro na memória (porque elas são de 32 bits, ou
quatro bytes).

A constante é somada com (PC)+4 por Adder e, na saída deste, é


disponibilizado o Branch target (Alvo do desvio), que é o endereço da
próxima instrução que deve ser executada no caso do desvio ser tomado.

O processador decidirá se o desvio deve ser tomado ou não de acordo com o


resultado computado pela ALU a partir de Read data 1 e 2. No caso das
instruções beq e bne, a ALU subtrai Read data 2 de Read data 1 (computa
$8 - $21, no caso da instrução bne $8, $21, 100).

Dependendo do resultado ser igual a zero ou não e da instrução ser beq ou


bne, a branch control logic (lógica de controle de desvio) copia o valor
branch target para o PC ou não.

aula20.doc 10 de 10
6.3 Uma Implementação Exemplo de um Processador
Agora estamos prontos para implementar um processador inteiro a partir dos
blocos básicos que estudamos. A figura abaixo mostra um datapath capaz de
executar instruções de leitura e escrita na memória e instruções lógicas e
aritméticas.

Neste datapath, o multiplexador mais à esquerda seleciona ou um registrador


ou uma constante de 16 bits retirada da própria instrução. Ele auxilia a
implementação de instruções com e sem operando imediato. Por exemplo:

add $2, $3, $4 # Estas instruções não têm operando imediato. O


sub $2, $3, $4 # multiplexador recebe um comando para colocar
# o valor do registrador na entrada inferior da ALU

addi $2, $3, 1000 # Para estas duas instruções, o multiplexador


lw $2, 1000($3) # recebe comando para colocar o valor do
# operando imediato 1000 na entrada inferior da
# ALU

aula21.doc 1 de 21
Os bits que compõem as primeiras duas instruções são como abaixo:

Formato da instrução add $2, $3, $4


nome op rs rt rd shamt funct
No. bits 6 5 5 5 5 6
decimal 0 3 4 2 0 32

Formato da instrução sub $2, $3, $4


nome op rs rt rd shamt funct
No. bits 6 5 5 5 5 6
decimal 0 3 4 2 0 34

Note que o campo op é igual para o add e o sub: é o campo funct que
diferencia as duas instruções.

Os formatos das instruções com operando imediato mostradas acima são:

Formato da instrução addi $2, $3, 1000


nome op rs rt imm
No. bits 6 5 5 16
decimal 4 3 2 1000

Formato da instrução lw $2, 1000($3)


nome op rs rt address
No. bits 6 5 5 16
decimal 35 3 2 1000

Note que os bits que determinam qual é o registrador destino da operação


(resultado da soma ou o valor lido da memória) não ficam no mesmo lugar
que nas instruções anteriores.

Note também que o campo de 16 bits mais a direita não tem o mesmo nome
para as instruções addi e lw.

O nome é diferente para indicar funções diferentes: na instrução addi este


campo é simplesmente um valor imediato enquanto que na instrução lw ele é
um valor imediato que ajuda a compor um endereço.

aula21.doc 2 de 21
Podemos adicionar ao datapath anterior os circuitos responsáveis por trazer
uma instrução da memória como demonstrado abaixo.

aula21.doc 3 de 21
Com mais alguns circuitos podemos fazer um datapath capaz de executar
instruções de desvio:

aula21.doc 4 de 21
As diversas instruções da MIPS ISA possuem formatos diferentes, cada um
para uma classe específica de instruções.

Para controlar o datapath adequadamente os bits dos campos de cada


formato têm que ser separados e levados a diferentes partes do datapath para
exercer o controle adequado à execução de cada instrução. A figura abaixo
mostra três classes de instrução da MIPS ISA.

aula21.doc 5 de 21
No datapath abaixo, os bits que compõem a instrução foram separados e
levados a diversas partes do datapath para exercer seu controle. Contudo,
alguns circuitos precisam de controle adicional, como o banco de
registradores, o multiplexador que determina Write Register, o multiplexador
na entrada inferior da ALU, etc, indicados na figura.

aula21.doc 6 de 21
PAREI AQUI: Para implementar o controle adicional incluímos uma
unidade de controle como indicado abaixo:

aula21.doc 7 de 21
A figura anterior mostra um processador completo, com um datapath e o
circuito de controle deste datapath. Este processador pode executar a
instrução add $2, $3, $4 seguindo os seguintes passos:

1. A instrução add $2, $3, $4 é lida da memória e o PC é incrementado


(fetch).
2. Os registradores $3 e $4 são lidos do banco de registradores. O
multiplexador mais à esquerda é posicionado de acordo com o formato da
instrução lida no passo 1.
3. O campo funct e o campo op da instrução são usados para especificar a
operação soma. O multiplexador central é posicionado de modo a colocar
na saída o valor do registrador $4. A ALU realiza a soma dos valores
lidos do banco de registradores.
4. O multiplexador na extrema direita é posicionado de modo a transferir a
saída da ALU (e não um dado lido da memória). O valor na saída do
multiplexador (resultado da soma computada pela ALU) é escrito no
registrador $2.

A implementação do controle do processador mostrada é combinatorial; isto


é, não existem, na verdade, os quatro passos indicados acima.

O datapath opera em um único ciclo de relógio e os sinais dentro do datapath


podem variar de forma imprevisível durante o ciclo. Os sinais estabilizam
aproximadamente na ordem indicada acima porque é nesta ordem que a
informação flui no datapath.

Para este processador funcionar corretamente, a duração do ciclo de clock


tem que ser ajustada para que, no final do ciclo, na subida do clock seguinte,
o dado a ser escrito em $2 e novo valor de PC estejam prontos para serem
escritos no banco de registradores e no registrador PC, respectivamente. Os
registradores do banco de registradores e o PC têm que ser registradores
acionados por borda.

aula21.doc 8 de 21
Vamos ver agora como a instrução lw $2, 1000($3) pode ser executada pelo
mesmo processador. Os passos abaixo mostram o fluxo de informações no
datapath durante a execução desta instrução:

1. A instrução lw $2, 1000($3) é lida da memória e o PC é incrementado


(fetch).
2. O registrador $3 é lido do banco de registradores. O multiplexador mais à
esquerda é posicionado de acordo com o formato da instrução lida no
passo 1.
3. O campo op da instrução é usado para especificar a operação soma. O
multiplexador central é posicionado de modo a colocar na saída o valor
imediato 1000. A ALU realiza a soma do valor lido do banco de
registradores com a constante imediata 1000.
4. A memória recebe um comando de leitura e a saída da ALU é usada
como endereço para o acesso de leitura.
5. O multiplexador na extrema direita da figura do processador é
posicionado de modo a transferir o dado lido da memória. O valor na
saída do multiplexador é escrito no registrador $2.

A instrução beq $3, $4, 1000 pode ser executada com os seguintes passos:

1. A instrução beq $3, $4, 1000 é lida da memória e o PC é incrementado


(fetch).
2. Os registradores $3 e $4 são lidos do banco de registradores.
3. O campo op da instrução é usado para especificar a operação subtração.
O multiplexador central é posicionado de modo a colocar na saída o valor
do registrador $4. A ALU realiza a subtração dos valores lidos do banco
de registradores e sua saída zero é posicionada de acordo com o
resultado. O valor de PC+4 é somado com o valor 1000 (com sinal
estendido) multiplicado por 4 (deslocado de d ois bits para a esquerda).
4. A saída zero da ALU é usada para decidir qual valor será escrito no
registrador PC no fim do ciclo: PC+4 ou PC+4 + (1000 * 4).

aula21.doc 9 de 21
6.4 Um Exemplo com Múltiplos Ciclos de Clock por Instrução
Na implementação do processador MIPS mostrada, todas as instruções
executam em um ciclo e, assim, o CPI de todas elas é igual a 1. Isso, no
entanto, faz com que o tempo de ciclo tenha que ser igual ao da instrução
que demora mais para executar.

Exercício:
Se assumirmos que em uma implementação de um processador MIPS:
a) As memórias tenham um tempo de acesso (da aplicação do endereço de
acesso até a disponibilização dos dados na saída) de 10 ns.
b) A ALU e os somadores demorem 10 ns para fazer suas operações.
c) O banco de registradores tenha um tempo de acesso de 5 ns.
d) Os multiplexadores, a unidade de controle, a unidade de extensão de
sinal, acessos ao PC e os fios conectando os componentes não atrasem a
propagação dos sinais.
Qual é o valor ideal do período de clock para as instruções add, lw e beq?
Qual é o período de clock que este processador tem que ter?

Resposta:
Para executar cada uma das instruções temos que:
add fetch leitura dos ALU escrita no
registradores registrador
lw fetch leitura dos ALU acesso à escrita no
registradores memória registrador
beq fetch leitura dos ALU
registradores

Assim, o período de clock ideal para a instrução add é igual a 10+5+10+5 =


30ns, para a instrução lw é igual a 10+5+10+10+5 = 40ns, e para a instrução
beq é igual a 10+5+10 = 25ns. Como o período de clock do processador tem
que ser igual ao período de clock necessário à execução da instrução mais
demorada, o período de clock deste processador tem que ser igual a 40ns.

aula21.doc 10 de 21
Através do exemplo dado por este exercício podemos ver que o ideal seria
termos um tempo de ciclo diferente para cada instrução ou uma solução
equivalente.

Uma solução elegante equivalente é usar mais de um ciclo para executar


cada instrução. Com esta solução é possível reutilizar unidades funcionais
durante a execução de uma instrução, o que permite implementar o
processador com menos componentes.

Em um processador implementado desta maneira, cada passo que usamos


para entender a execução de cada instrução poderia, por exemplo, ocorrer
em um ciclo independente.

Dependendo da instrução sendo executada, um número maior ou menor de


ciclos seria necessário. A tabela abaixo é um sumário dos passos necessários
para executar as instruções que estudamos.

Note que, considerando a tabela acima, a princípio três registradores novos


seriam agora necessários: A, B e Target.

Os registradores A e B guardariam temporariamente os valores lidos do


banco de registradores, enquanto que o registrador Target guardaria
temporariamente o endereço alvo de uma possível instrução de desvio.

Para cada ciclo de clock um dos passos é executado na ordem indicada na


tabela. Dependendo da instrução, alguns dos passos não são executados.

Observe que nenhum passo necessita de mais que uma operação de ALU ou
soma de endereço.

aula21.doc 11 de 21
Assim, apenas uma ALU bastaria para implementar todo o processador se
usássemos múltiplos ciclos para executar cada instrução: teríamos apenas
que direcionar os dados corretos para a ALU a cada ciclo.

A figura a seguir mostra como podemos implementar um processador para o


conjunto de instruções MIPS (MIPS ISA) desta nova forma (múltiplos ciclos
por instrução).

aula21.doc 12 de 21
aula21.doc 13 de 21
A unidade de controle deste processador não é combinacional, como a da
primeira implementação – esta unidade de controle é uma máquina de
estados. Um diagrama de blocos desta máquina de estados seria como
abaixo:

Uma descrição completa desta máquina de estados deve incluir todos os


sinais para controlar o processador, gerados a cada estado, e especificar
como ocorrem as transições entre os estados. A figura a seguir mostra a
máquina de estados completa.

aula21.doc 14 de 21
aula21.doc 15 de 21
Os círculos representam os estados e as setas indicam as transições de um
estado para outro. Uma transição de estado ocorre a cada ciclo de clock.

Quando mais de uma seta sai de um estado, como no estado 1, o estado


destino é aquele para o qual as condições indicadas na seta de transição
correspondente são verdadeiras.

Os sinais de controle gerados em cada estado estão dentro de cada círculo.

O número de ciclos de clock necessários para executar uma instrução é igual


ao número de estados que tem que ser percorridos para executá-la.

Esta máquina de estados pode ser implementada com um registrador de


quatro bits, e lógica combinacional para gerar o estado seguinte e as saídas
correspondentes a cada estado, como indicado na figura abaixo:

aula21.doc 16 de 21
6.5 Microprogramação
Implementar um processador como o discutido na seção anterior é fácil se a
ISA possui apenas umas poucas instruções. Mas mesmo a MIPS ISA, que é
uma ISA bem simples, possui cerca de 100 instruções.

A máquina de estados necessária para implementar um processador para a


MIPS ISA completa teria muitos estados e seria complicada de implementar.
No entanto, a implementação pode ser bastante simplificada através do uso
da microprogramação.

Uma unidade de controle implementada segundo a técnica da


microprogramação é como um pequeno computador dentro do computador.

O programa que esta micromáquina executa é chamado de microprograma e


cada sub-rotina do microprograma executa uma parte de uma instrução. Em
geral, as microinstruções que implementam o fetch são comuns a todas as
instruções e um segmento de microprograma adicional é usado para
implementar, ou emular, cada instrução.

aula21.doc 17 de 21
A figura abaixo mostra uma unidade de controle microprogramada.

aula21.doc 18 de 21
A figura abaixo mostra um microprograma, com 10 microinstruções, é capaz
de emular algumas instruções que estudamos usando a micromáquina acima
e o datapath da Figura 5.39.

M0
M1
M2
M3
M4
M5
M6
M7
M8
M9

Na figura acima, ALU control é a saída da lógica de mesmo nome da Figura


5.39;

SRC1 e SRC2 são os sinais de controle ALUSelA e ALUSelB,


respectivamente;

ALU destination é uma composição dos sinais TargetWrite e RegWrite;

Memory é uma composição dos sinais IorD, MemRead e MemWrite;

Memory register é uma composição dos sinais IRWrite e MemtoReg;

PCWrite control é uma composição de PCSource, PCWrite e PCWriteCond;

e Sequencing são as linhas de controle Sequencing control da Figura 5.52.

Assim como uma instrução de máquina pode ser representada


simbolicamente em linguagem assembly, uma microinstrução pode ser
representada simbolicamente, como fizemos no microprograma acima.

aula21.doc 19 de 21
Um micro-assembler é geralmente usado para converter o microprograma
simbólico em um microprograma em linguagem de micro máquina (bits).

Cada microinstrução simbólica da figura acima seria, então, transformada


em uma microinstrução binária por um micro-assembler específico para a
microarquitetura definida pelo controlador de microcódigo da Figura 5.52 e
pelo datapath da Figura 5.39.

Exercício: Calcule o número de bits necessário para codificar uma


microinstrução da micromáquina em estudo.

No microprograma da Figura 5.51, uma instrução lw é executada através da


seqüência de microinstruções M0, M1, M2, M3, M4.

Exercício: Indique quais seqüências de microinstruções executam um add e


um sw.

6.6 Exceções e Interrupções


Uma exceção é um evento inesperado que pode ocorrer dentro do
processador; um overflow, por exemplo.

Interrupções são eventos inesperados que ocorrem fora do processador; um


click no mouse provoca uma interrupção do processador, por exemplo.

Exceções e interrupções desviam o fluxo de execução dos programas para


rotinas específicas de tratamento de modo similar ao que fazem as instruções
de chamada de procedimento (jal). A figura abaixo mostra como dois estados
para tratar exceções poderiam ser incluídos na máquina de estados que
implementa o nosso processador. Estados adicionais poderiam ser incluídos
para tratar de interrupções.

aula21.doc 20 de 21
aula21.doc 21 de 21
6.7 Paralelismo Temporal e Espacial

Uma ALU permite a execução em paralelo de operações sobre vários bits (32,
64, etc.). Acima do paralelismo no nível de bit, temos o Paralelismo no Nível
Instrução (Instruction Level Paralelism – ILP). Existem dois tipos básicos de
ILP: temporal e espacial.

Pipelining realiza ILP temporal. Refere-se à segmentação da execução de uma


instrução em vários sub-processos que são executados por unidades autônomas
dedicadas (estágios do pipeline). Instruções sucessivas podem ser executadas em
um modo análogo à montagem de carros em uma fábrica.

Usando pipelining, várias instruções podem ser executadas em paralelo, cada


uma em um estágio diferente do pipeline, e cada uma em uma fase diferente de
sua execução.

Fetch Decode Execute Memory Write


Access Back

(a)

Clock Cycle 0 1 2 3 4 5 7

Fetch Stage I1 I2 I3 I4 I5 I6 I7
Decode Stage I1 I2 I3 I4 I5 I6
Execute Stage I1 I2 I3 I4 I5
M. Access Stage I1 I2 I3 I4
W. Back Stage I1 I2 I3

(b)

Porque as operações realizadas por cada estágio do pipeline são simples, cada
um destes estágios pode ser implementado com hardware simples, o que resulta
em uma máquina capaz de funcionar com uma alta frequência de clock.

Teoricamente, quanto mais profundo o pipeline (número maior de etapas de


pipeline), mais rápida a máquina; mas é óbvio que existem limitações práticas a
esta regra.

A primeira máquina pipelined de propósito geral foi a IBM 7030 Stretch


[Bloch59]. Após a IBM Stretch, a maioria das máquinas high-end tem usado
alguma forma de pipelining.

aula21a.doc 1 de 27
ILP espacial é aquele presente em processadores com múltiplas unidades
funcionais. Refere-se à execução de mais de uma instrução simultaneamente em
diferentes unidades funcionais do processador.

Paralelismo temporal e espacial podem estar presentes ao mesmo tempo em uma


máquina. Na verdade, poucos anos após a IBM 7030 Stretch ter sido construída,
a CDC6600 foi produzida com pipelining e várias unidades funcionais que
podiam funcionar em paralelo.

Instruction Memory Fetch

Decode Hardware Decode

Look-Ahead Dispatch Hardware Dispatch


Hardware

IW & Issue Hardware Issue

FU FU FU FU FU Execute

Write Back
Result Buses
Data Memory (b)

(a)

Neste curso vamos examinar apenas o paralelismo temporal no nível de


instrução.

aula21a.doc 2 de 27
6.8 Pipelining

A figura abaixo mostra uma versão simplificada de uma possível implementação


de um processador MIPS de 64 bits. Nesta implementação, todas as instruções
executam em um ciclo de clock.

A ISA MIPS 64 possui um conjunto de instruções um pouco maior que a ISA


MIPS 32. Isso explica o controle do MUX que define o próximo valor de PC na
figura acima.

aula21a.doc 3 de 27
Na figura abaixo á apresentado conjunto de instruções da MIPS 64.

aula21a.doc 4 de 27
A arquitetura anterior pode ser implementada usando pipelining como abaixo.

Com a implementação acima, a cada ciclo de clock, as instruções avançam de


um dos registradores em verde para o outro.

1. Inicialmente, no primeiro estágio, temos apenas o endereço da instrução


(Address) em PC.
2. No estágio seguinte, temos a instrução e seu endereço no registrador
IF/ID – este registrador demarca a saída do estágio de Fetch e a entrada
do estágio de Decode do pipeline.
3. No terceiro estágio, a instrução, seu endereço e os registradores que ela
endereça são escritos no registrador ID/EX - note que, mesmo que a
instrução não precise ler os registradores, ainda assim eles são lidos para
simplificar o hardware.
4. No quarto estágio, a operação que a instrução demandar da ALU é
realizada e:
a. Se a instrução for de desvio, o PC é atualizado e a instrução
terminada (é enviado um código “não faça nada” para o próximo
estágio).
b. Se a instrução for de outro tipo, ela e o resultado da operação
realizada pela ALU são transferidos para o próximo estágio.
5. No quinto estágio, se a instrução apenas envolver uma operação da ALU,
seu resultado é escrito no banco de registradores. Caso contrário, a
instrução é de acesso à memória e este acesso é feito. Caso a instrução
seja de leitura na memória, o dado lido é escrito no banco de registradores

aula21a.doc 5 de 27
A Figura abaixo ajuda a visualizar melhor a evolução da execução das instruções
no pipeline.

Na figura, a execução de uma sequencia de instruções (no lado direito) pode ser
analisada a cada ciclo de clock (colunas); isto é, podemos visualizar onde cada
instrução estará no pipeline a partir do ciclo onde a primeira entra no pipeline
(cycle 1).

Quando o pipeline está cheio, 5 instruções estão sendo executadas em paralelo.

A cada ciclo todas as instruções no pipeline se movem, avançando de um estágio


do pipeline. A figura abaixo mostra a execução de uma sequencia de instruções
no pipeline.

aula21a.doc 6 de 27
Na figura acima, não há dependências entre as instruções, mas elas podem
ocorrer, como no caso abaixo.

Na figura acima, três instruções têm sua execução afetada pela necessidade do
dado produzido pela primeira (r1). Ou seja, elas possuem dependência de dados
com a primeira.

Existem três tipos de dependência de dados: dependência verdadeira (read after


write – RAW), anti dependência (write after read – WAR), e dependência de
saída (write after write – WAW). Outro tipo de dependência importante é a
dependência de controle. Estes quatro tipos de dependência são ilustrados na
tabela abaixo.

aula21a.doc 7 de 27
I: add r1,r2,r3 I: sub r4,r1,r3
J: sub r4,r1,r3 J: add r1,r2,r3
K: mul r6,r1,r7
Dependência Verdadeira (RAW) Anti Dependência (WAR)
I: sub r1,r4,r3
J: add r1,r2,r3
K: mul r6,r1,r7
Dependência de Saída (WAW)

Dependência de Controle

No exemplo de dependência verdadeira de dados, as instruções I e J não podem


ser executadas em paralelo nem fora de ordem, já que a instrução J precisa do
conteúdo de r1, que será produzido pela instrução I.

No exemplo de anti dependência, dependendo do hardware I e J podem até ser


executadas em paralelo (se os dados forem lidos no início do ciclo e escritos no
final), mas não fora de ordem.

No exemplo de dependência de saída, I e J não podem ser executadas em


paralelo ou fora de ordem.

No exemplo de dependência de controle, vemos que as instruções de número 14,


18 e 22 só devem ser executadas se o desvio (beq) não for tomado: elas
possuem, então, dependência de controle com relação à instrução 10 (beq).

Em máquinas pipeline como as apresentadas, anti dependências e dependências


de saída não causam problemas, mas dependências verdadeiras e dependências
de controle precisam ser tratadas.

Na figura abaixo, o dado produzido pela primeira instrução precisa ser, de algum
modo, comunicado para as instruções que precisam dele em estágios anteriores
do pipeline.

aula21a.doc 8 de 27
aula21a.doc 9 de 27
Uma técnica conhecida como forwarding pode ser empregada para resolver o
problema de dependências verdadeiras dentro de pipeline. A figura abaixo ilustra
como a técnica forwarding resolve o problema de dependências de dados
verdadeiras dentro do pipeline.

O circuito de forwarding envia os dados necessários para cada estágio do


pipeline conforme necessário. A figura abaixo mostra exemplos, inclusive um de
um lw com um sw.

aula21a.doc 10 de 27
Contudo, há casos como o abaixo que não tem solução e uma “bolha” tem que
ser inserida no pipeline.

Na figura acima, o dado do lw só é produzido muito tarde para ser forwarded


(não pode haver forwarding para traz...). A figura abaixo mostra a bolha.

aula21a.doc 11 de 27
As figuras a seguir mostram a execução de uma sequencia de instruções sem
dependência de dados:

aula21a.doc 12 de 27
aula21a.doc 13 de 27
aula21a.doc 14 de 27
As figuras a seguir mostram a execução de uma sequencia de instruções com
dependência de dados e sem forwarding:

aula21a.doc 15 de 27
aula21a.doc 16 de 27
aula21a.doc 17 de 27
aula21a.doc 18 de 27
As figuras a seguir mostram a execução de uma sequencia de instruções com
dependência de dados e com forwarding:

aula21a.doc 19 de 27
aula21a.doc 20 de 27
aula21a.doc 21 de 27
As figuras a seguir mostram que, mesmo com forwarding, há casos que não é
possível evitar bolhas no pipeline:

aula21a.doc 22 de 27
aula21a.doc 23 de 27
A figura abaixo ilustra uma dependência de controle.

Dependências de controle provocam bolhas para trás (squash de instruções).

aula21a.doc 24 de 27
aula21a.doc 25 de 27
Note que, se não aproveitarmos os dados disponíveis no estágio de execução
(linhas em vermelho) para determinar o próximo PC, teremos que anular 3
instruções.

A arquitetura abaixo pode reduzir o número de instruções anuladas no caso de


desvios tomados para apenas uma:

aula21a.doc 26 de 27
Dependências de controle podem ser tratadas em máquinas pipelined
basicamente de quatro formas diferentes:
1. Parando (stall) o pipeline até que a direção do desvio seja conhecida
2. Predizendo que o desvio não vai ser tomado
3. Predizendo que o desvio vai ser tomado
4. Incluindo o conceito de desvio atrasado (delayed branch) na ISA

aula21a.doc 27 de 27
7 Hierarquia de Memória
7.1 Introdução
Os programadores sempre quiseram quantidades ilimitadas de memória
rápida desde a criação do primeiro computador.

Contudo, quanto maior a memória mais lenta ela é. Vamos usar o


exemplo de uma biblioteca para ilustrar por que.

Se você precisa escrever um relatório que requeira a pesquisa em uma


biblioteca e envolva a consulta a vários livros, uma boa estratégia para
minimizar o tempo gasto na pesquisa é pegar os livros que você acredita
necessários nas estantes da biblioteca, levá-los para uma mesa, consultar
os livros nesta mesa e ir escrevendo o relatório.

Se algum tópico não está coberto em nenhum dos livros na mesa, você
vai novamente às estantes da biblioteca e pega mais um livro contendo
aquele tópico.

Depois de algum tempo, todos os livros que você precisa vão estar na
mesa se ela for grande o suficiente para abrigá-los.

Esta estratégia toma muito menos tempo que trazer um livro para a mesa,
consultá-lo, devolvê-lo à estante e, só então, ir buscar um outro livro para
consulta.

Ou seja, a mesa se torna uma mini biblioteca muito mais rápida de


acessar que a biblioteca justamente porque ela é menor.

Do mesmo modo que você não pesquisa todos os livros da biblioteca com
igual probabilidade, um programa não acessa todos os dados e instruções
presentes na memória com igual probabilidade.

Assim, do mesmo modo que depois de algum tempo todos os livros de


que você precisa estão sobre a mesa, usando técnicas adequadas, é
possível ter as instruções e os dados de que o processador precisa mais
frequentemente mais próximos a ele em uma memória pequena e rápida.

Estas técnicas nos permitem criar a ilusão da existência de uma memória


grande e rápida que o processador possa acessar tão rápido como uma
memória rápida e pequena.

aula22.doc 1 de 7
As técnicas mencionadas acima são baseadas no princípio da localidade.
Existem dois tipos de localidade:
 Localidade temporal: Se um item é referenciado, ele tende a ser
referenciado novamente em um curto espaço de tempo;
 Localidade espacial: Se um item é referenciado, itens próximos a
ele tendem a ser referenciados também em um curto espaço de
tempo.

Os arquitetos de computador tiram vantagem do princípio da localidade


através da implementação da memória de forma hierárquica.

Uma hierarquia de memória consiste em múltiplos níveis de memória


com diferentes tamanhos e velocidades.

Memórias rápidas são, em geral, mais caras que memórias lentas e, por
isso, elas são, em geral, menores.

A memória principal dos computadores atuais é implementada com


DRAM (dynamic random access memory), enquanto que níveis de
memória mais próximos do processador são implementados com SRAM
(static random access memory). Além de DRAM e SRAM, computadores
atuais também usam HDs (hard disks).

O quadro abaixo mostra as velocidades e o preço por mega byte para


estes três tipos de memória em 1993.

aula22.doc 2 de 7
As relações de preço não mudaram muito desde então, como mostram as
tabelas a seguir.
Memórias DRAM

HDs

aula22.doc 3 de 7
No gráfico abaixo é apresentada a evolução do preço de diversos tipos de
memória de 1950 até o presente.

Devido às diferenças de preço e tempo de acesso, é vantajoso usar uma


hierarquia de memória com memórias caras, rápidas e pequenas próximas
ao processador, e memórias baratas, lentas e grandes mais afastadas do
processador, como mostrado na figura abaixo:

aula22.doc 4 de 7
Do mesmo modo que você encontra mais freqüentemente as informações
de que você precisa na mesa em que você está trabalhando na biblioteca,
o processador encontra mais freqüentemente as instruções e os dados de
que ele precisa nos níveis da hierarquia de memória mais próximos dele.

Os dados e instruções precisam ser copiados de tempos em tempos entre


os níveis da hierarquia de memória do mesmo modo que você precisa
levar ou trazer um livro da estante da biblioteca de tempos em tempos.

A menor unidade de informação que pode ser transferida entre os níveis


da hierarquia de memória é chamada de bloco.

Se o processador encontra a informação que ele procura em um


determinado nível da hierarquia de memória dizemos que houve um hit;
caso contrário, dizemos que houve um miss.

No caso de um hit, o bloco contendo a informação é copiado para o nível


mais alto de memória.

No caso de um miss, os próximos níveis, mais baixos na hierarquia, são


consultados até que ocorra um hit; o bloco contendo a informação é então
copiado nível acima até chegar junto ao processador.

O hit rate (taxa de acerto), ou hit ratio, é igual ao total de hits divido pelo
total de acessos em um determinado nível. A miss rate é igual a (1.0 – hit
rate), que é proporcional ao percentual de acessos que não encontraram a
informação em um determinado nível da hierarquia de memória.

aula22.doc 5 de 7
7.2 Cache
Uma memória cache é uma memória rápida que contém os dados e/ou
instruções mais recentemente referenciados pelo processador.

A figura abaixo mostra um cache simples antes e depois do acesso a um


dado no bloco Xn.

A figura acima deixa duas perguntas: (i) Onde procurar o dado dentro da
cache? (ii) Como saber se o dado de interesse realmente está neste lugar?

A forma empregada pelos caches para procurar um determinado bloco é


dividir o endereço de acesso pelo número de blocos e usar o resto da
divisão como endereço do bloco.

Se o número de blocos é igual a uma potência de 2, obter o resto da


divisão consiste simplesmente em separar uma porção dos bits de mais
baixa ordem do endereço igual à potência de dois mencionada (aquela
igual ao número de blocos).

Usando a técnica do resto da divisão estamos, na verdade, criando uma


associação entre um conjunto de endereços e um único bloco.

Para saber se o dado de um endereço específico está no cache, precisamos


guardar no bloco os demais bits do endereço para identificar unicamente
a que bloco da memória o bloco da cache corresponde. A figura abaixo
mostra como podemos implementar, então, uma memória cache:

aula22.doc 6 de 7
Na figura, os 14 bits de mais baixa ordem do endereço são usados para
escolher um dos blocos, onde pode estar o dado de interesse.

Os 16 bits de mais alta ordem são usados para confirmar se o dado na


cache é o dado desejado. Isso é feito por meio da sua comparação com os
16 bits da tag, armazenada junto com os dados na cache.

Na memória cache exemplo mostrada na figura, cada bloco da cache


armazena 4 bytes (32 bits). Se o processador deseja apenas um destes
bytes, o campo Byte offset do endereço é usado para escolher este byte
dentro do bloco.

aula22.doc 7 de 7
7.2.1 Cache hits e cache misses
Quando o processador acessa uma das caches (a de instruções ou a de
dados) pode ocorrer um miss ou um hit; muito mais hits do que misses, na
verdade. Primeiramente, vamos examinar o miss.

O processador, no lugar de apresentar o endereço à memória como vimos


anteriormente, apresenta o endereço à cache. A cache separa o endereço
em campos como indicado abaixo:

Quando o computador é ligado, todas as entradas da cache são inválidas,


ou seja, os bits valid são todos iguais a zero.

Assim, no primeiro acesso à cache, certamente ocorrerá um miss: a


entrada da cache endereçada pelos bits 2-15 do endereço vai conter um
bit valid igual a zero e a porta E (ver Figura 7.7) vai retornar zero na sua
saída, indicando que não houve um hit.

aula23.doc 1 de 17
O sinal hit, sendo igual a zero, informa ao processador para esperar até
que a cache possa entregar o conteúdo da posição de memória
endereçada.

Na ocorrência de um miss, a unidade de controle da cache (não mostrada


na figura) inicia, então, um acesso de leitura na memória, ou na cache
abaixo na hierarquia, no endereço inicialmente apresentado.

Quando a memória (ou a cache em posição mais baixa na hierarquia)


entregar o conteúdo da posição de memória endereçada, este valor é
escrito no campo data da entrada da cache apontada pelos bits 2-15 do
endereço e também entregue ao processador para que ele volte a
trabalhar.

Os bits 16-31 do endereço são gravados no campo tag da mesma entrada


e o bit valid recebe 1. Outros acessos a este mesmo endereço de memória
poderão, então, ler o dado da cache; ou seja, outros acessos ao mesmo
endereço resultarão em hits.

Observe que também ocorre um miss em um acesso à cache onde a


entrada correspondente ao endereço contém uma entrada válida, mas o
campo tag é diferente dos bits 16-31 do endereço. Este tipo de miss é
tratado da mesma forma.

Vamos examinar o que ocorre em um hit. Se a entrada da cache


endereçada pelos bits 2-15 do endereço é válida (valid = 1) e o campo tag
da cache for igual aos 16 bits de mais alta ordem do endereço (16-31)
então ocorreu um hit. Neste caso, o valor presente no campo data da
cache é entregue ao processador no lugar do conteúdo da memória
correspondente ao endereço acessado.

aula23.doc 2 de 17
A figura a seguir mostra uma pequena cache, com oito entradas, e o que
ocorre a cada miss para sequência de acessos de leitura: 10110 (miss),
11010 (miss), 10110 (hit), 11010 (hit), 10000 (miss), 00100 (miss), 10000
(hit), e 10010 (miss).

Memory (11010two)

Memory (10000two) Y

aula23.doc 3 de 17
7.2.2 Políticas de escrita na cache
Até aqui assumimos acessos de leitura apenas. Em acessos de escrita na
cache podem ocorrer hits ou misses mas, em qualquer dos casos, o dado
tem que ser escrito na memória.

Existem duas políticas básicas de escrita na cache: write through e write


back.

Na política write through sempre que o processador escreve na cache ele


também escreve, no mesmo momento, na memória. A figura abaixo
mostra o algoritmo seguido quando de acessos em caches write through.

O problema com estas caches é que o processador teria sempre que


esperar as escritas terminarem na memória para prosseguir executando o
programa.

Para evitar isso, caches write through usam um buffer de escrita na


memória. Este buffer é, na verdade, uma fila. Acessos de escrita na cache
são também escritos no buffer e este inicia imediatamente uma escrita na
memória.

aula23.doc 4 de 17
Acessos de escrita subsequentes vão sendo enfileirados no buffer ao
mesmo tempo em que os valores dentro dele vão sendo escritos na
memória. Se o buffer encher, aí sim o processador tem que esperar até
que haja uma entrada livre no buffer. O cache de dados da DECStation
3100 possui um buffer de 4 entradas.

Na política write back, acessos de escrita em entradas da cache com bit


valid igual a 0 não geram escritas na memória: o dado presente no bloco
só é escrito na memória quando ele vai ser substituído por um outro dado,
de outra posição na memória (miss de escrita), na cache.

Em um miss de escrita (tag diferente), a cache só escreve o dado na


memória quando o bloco da cache está “sujo” (dirty), isto é, possui um
dado escrito previamente.

Para saber se um bloco da cache possui um dado escrito previamente, é


usado um novo bit por bloco chamado dirty bit. O dirty bit de um bloco
recebe 1 toda vez que o bloco é escrito.

A figura abaixo mostra o algoritmo seguido quando de acessos em caches


write back.

aula23.doc 5 de 17
Um buffer de escrita pode também ser usado em caches com política de
escrita write back; contudo, eles são menos importantes neste caso, já que
a próprio cache atua como um buffer de escrita. A política write back é
mais complexa, mas resulta em menos misses.

A figura abaixo mostra o desempenho da cache que temos visto até agora
quando usada para dados e para instruções (a DECStation 3100 tem
caches iguais para dados e para instruções).

aula23.doc 6 de 17
aula23.doc 7 de 17
7.2.3 Tirando proveito da localidade espacial
Um observador atento deve ter notado que a cache que temos estudado
até agora não tira vantagem da localidade espacial, mas apenas da
localidade temporal. Isso porque cada bloco (entrada) da cache vista até
agora só acomodava uma palavra de memória.

Para tirar vantagem da localidade espacial, a cache tem que possuir


blocos com mais de uma palavra de memória. Quando da ocorrência de
um miss, esta nova cache traz da memória não só a palavra endereçada,
mas também suas vizinhas.

Se existir localidade espacial, novos acessos provavelmente endereçarão


estas palavras vizinhas, que já estarão na cache. A cache da figura abaixo
tira vantagem da localidade espacial dos acessos à memória:

aula23.doc 8 de 17
Na cache da Figura 7.9, cada bloco possui quatro palavras de 32 bits.
Quando ela é acessada, o endereço é separado em campos como indicado.
Um novo campo foi incluído: o campo block offset.

Este campo vai dizer qual das palavras de um bloco está sendo acessada.
O multiplexador da figura separa esta palavra usando o campo block
offset.

Acessos de leitura, sejam eles hits ou misses, são tratados nesta cache da
mesma forma que na cache com blocos de uma palavra apenas. Escritas
que resultam em hits também são tratadas como anteriormente, seja a
cache write through ou write back. Acessos de escrita que resultam em
misses, por outro lado, têm que ser tratados de forma diferente.

Em acessos de escrita que resultam em misses não podemos


simplesmente escrever no bloco da cache uma palavra de dado e a tag
como anteriormente, já que, assim fazendo, estaremos colocando neste
bloco da cache dados pertencentes a diferentes blocos de memória (as três
palavras do conteúdo antigo do bloco e a palavra nova que gerou o miss).

No caso de misses em caches com blocos maiores que uma palavra de


memória, primeiro o bloco da memória correspondente ao endereço
acessado tem que ser lido. Só depois disso podemos escrever a palavra no
bloco da cache. Isso garante que os blocos da cache sejam sempre
equivalentes aos blocos da memória.

aula23.doc 9 de 17
A figura abaixo mostra como os desempenhos das caches da DECStation
3100 melhoram se mudarmos o tamanho do bloco das caches para quatro
palavras e mantivermos o restante (a capacidade do cache,
principalmente) inalterado.

aula23.doc 10 de 17
O desempenho melhora, principalmente o da cache de instruções, porque
agora estamos tirando proveito da localidade espacial. Contudo, se o
bloco é muito grande comparado com o tamanho total da cache, o
desempenho pode cair.

Isso ocorre porque, neste caso, o número de blocos ficará pequeno e


haverá uma grande disputa por estes blocos. A figura abaixo mostra como
o tamanho bloco e o tamanho total da cache afetam seu desempenho.

Um outro problema associado com blocos grandes é o aumento do custo


dos misses que eles impõem. O custo de um miss é proporcional ao
número de acessos à memória que tem que ser feitos para servi-lo. Blocos
muito grandes podem precisar de muitos acessos à memória para serem
atualizados. Contudo, freqüentemente basta um acesso para preencher um
bloco da cache.

aula23.doc 11 de 17
7.2.4 Formas de comunicação cache-memória
A forma básica de se ligar uma cache à memória é a da Figura 7.12a,
abaixo:

Contudo, se o tamanho do bloco da cache é maior que uma palavra de


memória, podemos usar a opção mostrada na Figura 7.12b, que permite
que se leia ou se escreva um bloco inteiro de uma vez.

Uma terceira opção, Figura 7.12c, é usar a técnica de interleaving. Nesta


técnica, a memória é dividida em quatro bancos que podem ser acessados
em paralelo.

As palavras de memória com endereços terminados em 00 (binário) são


guardadas no banco de memória 0, as terminadas em 01 no banco 1, em
10 no banco 2 e as terminadas em 11 no banco 3.

Exercício. Quanto tempo demora a transferência de um bloco da memória


para o cache em cada uma das opções da Figura 7.12 se são necessários:
10 ciclos para acessar a memória, 1 ciclo para enviar o endereço de
acesso até a memória, e 1 ciclo para transferir uma unidade de dados
através do barramento memória-cache.

aula23.doc 12 de 17
7.2.5 Associatividade das caches
Podemos melhorar o desempenho das caches ainda mais se pudermos
procurar por um dado em mais de um bloco ao mesmo tempo. Ou seja, se
pudermos fazer o que é conhecido como uma busca associativa pelo dado
na cache.

Em uma busca associativa, no lugar de fazermos um acesso baseado


apenas no endereço, fazemos um acesso baseado também no conteúdo da
cache; mais especificamente, baseado no conteúdo do campo tag.

A figura abaixo mostra uma cache de oito blocos organizada com


diferentes níveis de associatividade:

Uma cache com associatividade igual 1 (direct mapped) é como as que


vimos até agora: cada endereço corresponde a um bloco apenas.

aula23.doc 13 de 17
Em uma cache com associatividade igual a dois, um único endereço está
associado a dois blocos: para se saber qual é o bloco correto as duas tags
(ver Figura 7.23) têm que ser comparadas com a parte alta do endereço.
Caches com maior grau de associatividade requerem mais comparações.

Ao conjunto de blocos correspondente a um único endereço é dado o


nome de set. Um set de uma cache direct mapped contém um único
bloco, um set de uma cache com associatividade 2 contém dois blocos,
com associatividade 4, quatro blocos, e assim por diante.

A figura abaixo mostra como é organizada uma cache com


associatividade 4, 256 sets e blocos de uma palavra de tamanho.

aula23.doc 14 de 17
Quando um ocorre um miss em uma cache com associatividade maior que
1 temos que decidir que bloco deve ser substituído.

Em uma cache totalmente associativa, todos os blocos são candidatos a


serem substituídos. Já em uma cache associativa por sets, temos que
escolher entre os blocos de um dos sets apenas.

Existem duas estratégicas básicas de seleção do bloco a ser substituído:


randomicamente ou o least recently used – LRU (o bloco usado há mais
tempo). A estratégia que oferece melhor desempenho é a LRU, mas ela é
mais difícil de implementar.

A figura abaixo mostra o desempenho de várias configurações de cache e


destaca as diferenças no desempenho causadas pelos diferentes graus de
associatividade:

aula23.doc 15 de 17
7.2.5 Entendendo o comportamento das caches
Existem três tipos de miss:
1. Compulsório: O primeiro acesso a um bloco de memória é um miss
compulsório (não pode ser evitado).
2. De capacidade: Se uma cache é pequena demais para conter todos
os blocos necessários à execução de um programa, ocorrerão
misses de capacidade devido aos blocos que têm que ser
descartados e depois relidos.
3. De conflito: Em caches direct mapped ou associativas por sets,
mais de um bloco de memória é mapeado no mesmo bloco ou
conjunto de blocos da cache. Quando ocorrem acessos a blocos de
memória associados ao mesmo set em maior número que o número
de blocos no set, teremos misses de conflito.

A figura abaixo mostra o desempenho de várias configurações de cache e


destaca os três tipos de miss apresentados:

aula23.doc 16 de 17
Exercício: A figura abaixo mostra uma cache com associatividade 4, 256
sets e blocos de uma palavra de tamanho (32bits). A política de
substituição de blocos é LRU. Supondo que, inicialmente:
 todos os blocos têm bit de válido igual a zero
 os blocos sejam numerados da direita para esquerda e que todos
eles tenham sido usados em ordem uma vez (o primeiro a ser usado
sendo o da esquerda e o último o da direita)
diga que blocos de quais sets (em hexadecimal) são modificados se a
sequência de escritas abaixo é realizada pelo processador.

Escrita Endereço Dado


1 0101 0101 1111 0000 0101 0101 1111 0000 0000 0000 0000 0000 0000 0000 0000 0000
2 1111 1111 1111 1111 1111 1111 1111 1100 0000 0000 0000 0000 0000 0000 0000 0000
3 0101 0101 1111 0101 0101 0101 0101 0100 1111 1111 1111 1111 1111 1111 1111 1111
4 1111 1111 1111 1111 1111 1111 1111 1100 1111 1111 1111 1111 1111 1111 1111 1111
5 1111 1111 1111 1110 1111 1111 1111 1100 1111 1111 1111 1111 1111 1111 1111 1111

aula23.doc 17 de 17
7.3 Memória Virtual
Do mesmo modo que caches permitem acesso rápido a instruções e dados
armazenados na memória e usados recentemente pelo processador, a
memória também pode permitir acesso rápido aos dados e programas
armazenados em unidades de armazenamento secundário (usualmente discos
rígidos).

A técnica usada para isso é conhecida como memória virtual. Existem duas
motivações principais para o uso desta técnica: (i) permitir o
compartilhamento eficiente da memória entre vários programas, e (ii)
remover as dificuldades de programação introduzidas por uma memória
principal pequena.

Considere uma coleção de programas rodando em uma máquina ao mesmo


tempo. A memória total necessária para a execução destes programas pode
ser muito maior que a memória física do computador; contudo, pode ocorrer
de apenas uma fração da memória total necessária por todos os programas
ser de fato usada em qualquer ponto da execução destes programas.

A memória principal precisa, então, conter apenas a parte “ativa” dos


programas, do mesmo modo que caches contém apenas a parte ativa de um
programa. A técnica de memória virtual nos permite partilhar eficientemente
a memória entre vários programas.

A segunda motivação para o uso de memória virtual é permitir que os


programas excedam o tamanho da memória principal. Antigamente, quando
um programa ficava grande demais para a quantidade de memória
disponível, os programadores tinham que resolver o problema através de
técnicas de programação.

A técnica mais utilizada era a técnica de overlay. Nesta técnica, os


programadores dividiam o programa em partes mutuamente exclusivas. Uma
parte do programa ficava sempre na memória e comandava o processo de
trazer as partes adicionais necessárias durante a execução do programa.

Claramente, implementar um programa desta forma adicionava bastante


trabalho de programação.

aula24.doc 1 de 15
A memória virtual, que foi inventada para tirar este trabalho dos
programadores, gerencia automaticamente os dois níveis de hierarquia de
memória representados pela memória principal e pelas unidades de
armazenamento secundário.

Quando compilamos um programa é impossível saber quais outros


programas vão dividir a memória com ele. Assim, o ideal seria compilar
cada programa para que ele ficasse em um espaço de endereçamento
exclusivo.

Como vários programas podem estar sendo executados ao mesmo tempo,


temos também que proteger um programa de acessos de escrita de outros
programas.

Um sistema de memória virtual pode prover ambos: espaço de


endereçamento exclusivo para cada programa e proteção deste espaço de
endereçamento contra escritas alheias a ele.

Embora os conceitos usados em caches e memórias virtuais sejam os


mesmos, como iremos ver, raízes históricas diferentes levaram ao uso de
terminologias diferentes.

Um bloco de memória virtual é chamado de página e um miss em um acesso


à memória virtual é chamado de page fault.

Em um computador com memória virtual, o processador produz um


endereço virtual quando do acesso à memória.

Este endereço virtual é traduzido, por uma combinação de hardware e


software, para um endereço físico, que é então utilizado para acessar a
memória principal. Este processo é conhecido como address translation.

Para simplificar a address translation, sistemas de memória virtual dividem


a memória em páginas de tamanho fixo (usualmente entre 4Kbytes e
16Kbytes).

Assim, o mapeamento de um endereço virtual para um endereço físico se


torna um mapeamento de uma página virtual para uma página física. A
figura abaixo mostra, de forma simplificada, como isso é feito.

aula24.doc 2 de 15
Através da relocação de endereços, um sistema de memória virtual também
simplifica a tarefa de carregar um programa na memória para execução.

Em um sistema com memória virtual, os endereços virtuais dos programas,


escolhidos durante a compilação, podem ser mapeados para endereços
físicos totalmente diferentes.

Ou seja, uma página virtual de um programa pode ser mapeada para


qualquer página da memória física durante a execução.

A figura abaixo mostra de forma esquemática como pode ser feito o


mapeamento de um endereço virtual para um endereço físico.

aula24.doc 3 de 15
No mapeamento acima, uma página possui 4Kbytes. O número máximo de
páginas físicas permitido é igual a 218, o que resulta em uma memória física
de no máximo 1Gbyte. O espaço de endereçamento virtual, contudo, pode
ter 4Gbytes.

O custo de um page fault é extremamente alto. Isso porque page faults


envolvem a movimentação de muitos bytes entre memória e disco, sendo o
maior custo aquele da espera pela transferência do primeiro byte.

Este custo (que chega a milhões de ciclos de processador) determina muitas


das características dos sistemas de memória virtual:
 As páginas têm que ser grandes para amortizar o custo da
transferência do primeiro byte.
 A memória virtual tem que ser organizada de modo a reduzir a
frequência de page faults devido ao seu alto custo em termos de
número de ciclos de processador.
 Page faults podem ser tratados por software, já que eles
necessariamente demoram muitos ciclos.
 Não é possível empregar a política de escrita write-through porque ela
envolve acessos mais frequentes de escrita nos discos. A política de
escrita em memória virtual é basicamente write-back.

aula24.doc 4 de 15
7.3.1 Mapeamento Memória Virtual - Memória Física
Uma página virtual pode ser mapeada em qualquer página física. Assim,
sistemas de memória virtual são fully associative (totalmente associativos).

Se uma página virtual pode ser mapeada para qualquer página física, nós
precisamos de um mecanismo para encontrá-la na memória física. Este
mecanismo é implementado através de tabelas de páginas.

As tabelas de páginas ficam na memória e são também organizadas na forma


de páginas – uma tabela de páginas é tipicamente composta por várias
páginas de memória.

Cada programa tem a sua própria tabela de páginas, que mapeia seus
endereços virtuais (escolhidos durante a compilação) aos seus endereços
físicos (escolhidos pelo sistema operacional durante a execução).

Para achar a tabela de páginas na memória, processadores com hardware


para implementação de memória virtual usam um registrador chamado de
page table register.

Quando o sistema operacional (S.O.) passa o controle do processador para


um programa, ele carrega este registrador com o endereço da tabela de
páginas do programa.

A figura abaixo mostra como o mapeamento virtual-físico pode ser feito


usando o que estudamos até agora.

aula24.doc 5 de 15
Na figura, parte do endereço virtual é mapeado diretamente para o endereço
físico por meio dos bits conhecidos como page offset.

Estes bits escolhem qual endereço dentro da página que o processador deseja
acessar e não precisam ser manipulados pelo processo de tradução do
endereço virtual para o endereço físico.

O restante dos bits do endereço virtual determina qual é a página virtual


sendo acessada e precisa ser traduzido para o número da página física. A
tradução não passa de uma consulta à tabela de páginas.

Na verdade, estes bits são um índice para a tabela e apontam para a entrada
dela que contém o número da página física correspondente à página virtual.

aula24.doc 6 de 15
Um bit de válido na tabela indica se existe de fato esta correspondência, isto
é, se existe uma página física associada à página virtual desejada pelo
processador.

7.3.2 Page Faults


Quando o usuário comanda o S.O. para que ele inicie a execução de um
programa, o S.O. lê o programa no disco para saber quantas páginas ele vai
precisar.

Neste processo, o S.O. monta a tabela de páginas do programa e a coloca em


páginas de memória específicas escolhidas por ele, o S.O. Note que, neste
momento, o programa em si não foi transferido para a memória: as entradas
de sua tabela de páginas apontam para posições físicas do disco.

O S.O., então, passa o controle do processador ao programa, escrevendo o


endereço da primeira instrução do programa no PC e o da tabela de páginas
no page table register, além de outras inicializações que não discutiremos
aqui.

Quando o processador tenta ler a primeira instrução do programa na


memória, ele faz um acesso a uma página cujo bit de válido na tabela de
páginas é igual a zero.

O processador é então avisado de que houve um page fault, através de uma


excessão de hardware. Na ocorrência de um page fault, o S.O. toma o
controle do processador.

Ele então tem que:


(i) achar a página requisitada pelo programa no disco;
(ii) escolher uma página física para colocar a página trazida do disco;
(iii) transferir a página do disco para a página física escolhida;
(iv) atualizar a tabela de páginas; e
(v) transferir o controle do processador ao programa outra vez. Desta vez,
no entanto, o processador vai encontrar a instrução na memória e
poderá executar o programa enquanto ele não exigir a execução de
instruções (ou acesso a dados) fora da página inicial.

aula24.doc 7 de 15
Sempre que o processador fizer acessos a páginas, seja para instruções ou
dados, cujos bits de válido são iguais a zero, ele será interrompido e chamará
o S.O. para que ele:
i. ache a página requisitada pelo programa no disco;
ii. escolha uma página física para colocar a página trazida do disco;
iii. transfira a página do disco para a página física escolhida;
iv. atualize a tabela de páginas;
v. e transferira o controle do processador ao programa outra vez.

Assim, durante a execução de um programa, parte de suas páginas estará


mapeada no disco e parte de suas páginas estará mapeada na memória, como
mostrado na figura baixo.

Quando ocorre um page fault e todas as páginas da memória física estão


ocupadas, uma delas tem que dar seu lugar para a página requisitada.

A técnica usualmente utilizada para escolher a página vítima da substituição


é uma aproximação da LRU (least recently used – usada há mais tempo).

aula24.doc 8 de 15
Para ajudar o S.O. a ter uma ideia de qual página foi usada há mais tempo, as
implementações de memória virtual geralmente incluem um vetor de estado,
com um conjunto de bits para cada página física, que servem para indicar se
uma página física foi acessada e de que modo.

Um bit do vetor de estado é ligado pelo sistema de memória virtual sempre


que a página correspondente é acessada.

Um ponteiro do sistema de memória virtual aponta, inicialmente, para o


primeiro elemento do vetor e, toda vez que uma página de memória física é
necessária, este vetor é percorrido a partir da posição atual do apontador.

Se uma posição contendo 1 é encontrada, ela é modificada para zero e o


apontador é avançado. Se uma posição contendo zero é encontrada, a página
correspondente é utilizada.

Um outro bit do vetor de estado é o dirty bit, que indica se uma página foi
escrita. Se uma página escolhida para dar lugar à outra em um page fault foi
escrita ela precisa ser copiada de volta no disco antes de ser sobrescrita, caso
contrário, ela pode simplesmente ser sobrescrita.

PAREI AQUI 7.3.3 Translation-Lookaside Buffer – TLB


Tabelas de páginas são em geral grandes e precisam ser guardadas na
memória – seria muito custoso mantê-las dentro do processador.

Mas, para fazer um acesso a um endereço virtual, o processador precisa


traduzi-lo para um endereço físico, o que envolve uma leitura na tabela de
páginas.

Isso implica que, sem hardware adicional, todo acesso à memória em um


computador com memória virtual exigiria, na verdade, dois acessos: um na
tabela de páginas e outro na página da memória física associada à página
virtual.

aula24.doc 9 de 15
PTR

Na verdade, atualmente as tabelas de páginas são organizadas em mais de


um nível para diminuir a quantidade de páginas de memória física destinadas
à tabelas de página.

Em um sistema de memória virtual com dois níveis de tabelas de páginas


seriam necessários três acessos à memória física para cada acesso à memória
virtual!

aula24.doc 10 de 15
Para evitar isso, computadores com memória virtual possuem um pequeno
cache com as traduções de endereços virtuais para endereços físicos mais
recentes chamado de TLB.

A figura abaixo mostra de forma simplificada um sistema de memória virtual


com TLB.

aula24.doc 11 de 15
TLBs são pequenos – usualmente possuem entre 64 e 512 entradas. Cada
entrada guarda o número da página física correspondente a uma página
virtual, um bit de válido, além de outros bits. TLBs são em geral fully
associative e o número da página virtual é usado como tag do TLB.

A figura abaixo mostra uma organização típica de TLB. Nela, 20 bits do


endereço virtual determinam qual é a página e 12 bits determinam que dado
dentro da página é desejado pelo processador.

Se os 20 bits que indicam a página são iguais a qualquer uma das entradas
do TBL, o número da página física guardado no TLB é usado para acessar a
memória junto com os 12 bits que indicam o dado desejado.

Na verdade, o endereço físico não é usado para acessar a memória


diretamente, mas sim um dos caches (ver figura).

aula24.doc 12 de 15
Quando o processador tenta um acesso à memória ele consulta primeiro o
TLB para ver se já existe nele uma tradução da página virtual desejada para
a página física correspondente.

Caso não exista, temos um TLB miss. TLB misses podem ser tratados por
hardware, ou por software via exceção (a diferença de desempenho entre
estas duas opções é pequena).

Quando de um TLB miss, a tabela de páginas, que está na memória, tem que
ser consultada.

aula24.doc 13 de 15
Se a página correspondente ao endereço virtual acessado pelo processador já
estiver mapeada na memória física, este mapeamento (o número da página
física) é copiado para o TLB junto com outros bits, como o dirty bit da
página por exemplo.

Caso contrário, ocorre uma interrupção e o S.O. é invocado para tratar do


page fault, conforme já discutimos.

Quando ocorre um TLB miss e um novo mapeamento virtual-físico tem que


ser trazido para dentro do TLB, pode ocorrer de não haver nenhuma entrada
livre no TLB.

Nestes casos, uma das entradas do TLB tem que ser selecionada para ser
sobrescrita. A seleção é feita randomicamente ou usando a política LRU.
Quando esta entrada contiver informações sobre uma página de memória
que foi escrita, os bits associados à página e guardados no TLB têm que ser
escritos de volta na tabela de páginas para manter a consistência da tabela de
páginas.

A figura abaixo apresenta o algoritmo que tem que ser obedecido em todos
os acessos à memória feitos em um computador que empregue memória
virtual.

aula24.doc 14 de 15
aula24.doc 15 de 15
8. Entrada e Saída (Input/Output – I/O)
8.1 Introdução
Um computador é composto pelo processador, memória e dispositivos de
I/O. Nós já estudamos o processador e a memória e, no início do curso,
alguns dispositivos de I/O. Agora vamos estudar alguns mecanismos de
I/O.

A figura abaixo mostra como um computador é usualmente organizado


hoje.

Na figura, podemos ver que vários dispositivos de I/O distintos podem


fazer parte de um computador e estar conectados a um único processador.
Circuitos controladores de I/O são usados para permitir a comunicação
entre o processador e os dispositivos de I/O.

aula25.doc 1 de 12
As velocidades exigidas de cada dispositivo de I/O variam fortemente. De
alguns dispositivos não se requer, hoje, mais velocidade. Nesta categoria
poderíamos incluir o teclado e o mouse, por exemplo.

De outros dispositivos, contudo, sempre parece ser desejável mais


velocidade, como é o caso dos discos e das redes de comunicação, por
exemplo. A figura abaixo mostra exemplos de vários dispositivos de I/O e
suas velocidades em 2005.

aula25.doc 2 de 12
8.2 Performance de Entrada/Saída (Input/Output – I/O)
Os principais parâmetros de desempenho de I/O são latência e taxa de
transferência (throughput).

A latência, ou tempo de resposta, de um dispositivo de I/O é o tempo que


ele demora do pedido até a transferência do primeiro byte (ou word) de
uma operação de I/O, enquanto que a taxa de transferência é o número de
bytes transferidos por segundo em uma operação de I/O.

Assim como no caso dos processadores, para medir o desempenho de


sistemas de I/O são usados programas de benchmark. Um dos tipos de
operações de I/O mais importantes hoje é o processamento de transações
(transaction processing – TP). Programas para TP tipicamente fazem uma
grande quantidade de pequenos acessos de escrita em um banco de dados
em um curto espaço de tempo.

Um exemplo de TP é a atualização de contas bancárias oriunda de


operações em caixas de banco e caixas automáticos. Um conjunto de
programas de benchmark conhecido como TPC-B incluia um programa
que simula a atualização de contas bancárias conforme descrito.

Uma medida padrão de desempenho de I/O importante é, então, o número


de transações por segundo, ou transactions per second (TPS). Em 1993
um TPS típico para máquinas de alto desempenho com um processador
era igual a 300.

Existe uma série de benchmarks para medida de desempenho de


transações de I/O (http://www.tpc.org). O TPC-E, por exemplo, mede o
desempenho de sistemas que cuidam de contas de clientes de uma
corretora de valores. Em 2015, o melhor desempenho conhecido neste
benchmark era igual a 10.058 TPS.

Outro tipo importante de benchmark de I/O é o benchmark de sistemas de


arquivos. Este tipo de benchmark faz acessos de leitura e escrita em
arquivos, copia de arquivos, etc.

8.3 Barramentos

Barramentos podem ser classificados em: barramentos de memória


(processor-memory bus), barramentos de I/O (I/O bus) e barramentos
intermediários (backplane bus).

aula25.doc 3 de 12
Barramentos de memória conectam o cache do processador à memória
principal. Eles são os mais rápidos e, por isso, são geralmente curtos
(poucos centímetros separam a CPU da memória) devido à velocidade de
propagação dos sinais nos fios, limitada pela velocidade da luz.

Diferente dos barramentos de memória, barramentos de I/O são


usualmente lentos e longos. Diversos tipos de dispositivos de I/O podem
ser, em geral, conectados a eles.

Barramentos intermediários são projetados para permitir que a CPU, a


memória e dispositivos de I/O possam coexistir em um único barramento.

Barramentos de memória são, em geral, específicos para cada fabricante,


enquanto que barramentos de intermediários e de I/O são, em geral,
usados por vários fabricantes.

aula25.doc 4 de 12
A figura abaixo mostra exemplos de como os diferentes tipos de
barramentos podem ser usados em conjunto.

aula25.doc 5 de 12
Barramentos podem ser síncronos ou assíncronos. Um barramento
síncrono possui um clock que determina o momento de todas as
operações: todos os dispositivos têm que realizar suas operações dentro
de um número de ciclos de clock e os momentos em que os dados e
endereços têm que ser apresentados, relativos à subida e/ou descida do
clock, também têm que ser precisos.

Barramentos síncronos são simples e rápidos. Contudo, todo dispositivo


ligado a um barramento síncrono tem que operar na freqüência do
barramento e, devido à velocidade de operação, barramentos síncronos
têm que ser curtos. Barramentos de memória são, em geral, síncronos.

Barramentos assíncronos não são comandados unicamente por um clock


(muito embora possam fazer uso de um clock). Graças a isso, dispositivos
com diferentes velocidades de operação podem ser conectados a eles.
Para coordenar a transmissão de dados entre transmissor e receptor,
barramentos assíncronos usam protocolos de handshaking (aperto de
mão).

A figura a seguir mostra um protocolo de handshaking típico:

aula25.doc 6 de 12
Na Figura 8.10, os sinais ReadReq, Ack, e DataRdy são sinais de controle
de um bit e são carregados em um fio, enquanto que Data carrega os
sinais de dados e endereços e é implementado com 8 fios, 16 fios, 32 fios,
etc, dependendo do tamanho dos dados ou endereços sendo comunicados
através do barramento.

Barramentos assíncronos operam, em geral, sob o controle de duas


entidades, um Mestre (Master) e um Escravo (Slave), que podem, em
diferentes momentos, ser representados pela CPU, I/O ou memória, sendo
que muitas vezes qualquer um deles pode ser o Mestre ou o Escravo.

Na figura anterior, o sinal ReadReq é usado para indicar que o Mestre do


barramento quer fazer uma leitura. O sinal DataRdy é usado pelo Escravo
para indicar que os dados estão prontos nas linhas Data. O sinal Ack é
utilizado pelo Mestre ou pelo Escravo para informar que recebeu o sinal
DataRdy ou ReadReq, respectivamente.

O protocolo de uma operação de leitura começa com o Mestre do


barramento colocando o endereço de leitura em Data e ligando o sinal

aula25.doc 7 de 12
ReadReq, e segue como indicado nos passos abaixo (acompanhe pela
Figura 8.10):
1. Quando o Escravo “vê” o sinal ReadReq ligado, ele lê o endereço e
liga o sinal Ack para indicar que o endereço já foi lido.
2. O Mestre percebe que o sinal Ack está ligado, desliga o sinal
ReadReq e libera as linhas Data.
3. O Escravo percebe que o sinal ReadReq foi desligado e desliga o
sinal Ack.
4. Quando o Escravo tem pronto o dado pedido pelo Mestre, ele liga
o sinal DataRdy e coloca o dado em Data.
5. Quando o Mestre vê que DataRdy está ligado, ele lê o dado de
Data e liga o sinal Ack.
6. O Escravo percebe que o sinal Ack está ligado e desliga o sinal
DataRdy e libera as linhas Data.
7. O Mestre percebe que DataRdy foi desligado e desliga o sinal Ack.
Isto termina a transferência.

Para realizar operações de escrita um novo sinal de controle, WriteReq,


seria utilizado, mas o protocolo seria o mesmo.

aula25.doc 8 de 12
8.4 Direct Memory Access (DMA)
A figura abaixo mostra o protocolo de handshaking de leitura descrito na
forma de máquinas de estado. Na figura, o Mestre é o I/O Device
(dispositivo de I/O) e o Escravo é a Memory (memória). Dispositivos de
I/O capazes de acessar diretamente a memória são conhecidos como
dispositivos com capacidade de DMA (Direct Memory Access).

aula25.doc 9 de 12
8.5 Arbitração de Barramentos
Para se tornar Mestre do barramento, a CPU, memória ou os dispositivos
de I/O tem que “pedir” o barramento. O processo de solicitação e entrega
do barramento a um Mestre é chamado de arbitração de barramento (bus
arbitration).

Existem quatro tipos básicos de arbitração de barramento.


1. Daisy chain: Neste esquema de arbitração, um sinal, que informa
se o barramento pode ser tomado (grant), segue do dispositivo de
maior até o dispositivo de menor prioridade, passando através de
cada um deles. Quando um dispositivo quer o barramento, ele
desabilita o sinal grant saindo dele, e verifica se sinal grant
chegando a ele está ligado e se o barramento está livre; neste caso,
ele toma o barramento para si.
2. Arbitração centralizada e paralela: Neste esquema de arbitração,
existem várias linhas de solicitação, e os dispositivos solicitam o
barramento independentemente. Um circuito árbitro centralizado
decide e libera o barramento para um dos solicitantes.
3. Arbitração distribuída via auto-arbitração: Este esquema também
usa múltiplas linhas de solicitação, mas os próprios dispositivos
pedindo o barramento determinam qual deles tomará o barramento.
Cada dispositivo pedindo o barramento coloca o seu código nele.
Através da análise dos códigos presentes no barramento, os
dispositivos têm como saber quem tem maior prioridade, e este
toma o barramento.
4. Arbitração distribuída via detecção de colisão: Neste esquema,
cada dispositivo pode pedir o barramento independentemente.
Múltiplos dispositivos pedindo o barramento ao mesmo tempo
causam uma colisão. Colisões são detectadas pelos próprios
dispositivos e estes usam um esquema, específico para cada
barramento, para saber qual deles tomará o barramento.

aula25.doc 10 de 12
8.6 I/O Serial
Quando o I/O é serial apenas um bit pode ser transferido de cada vez. I/O
serial pode ser síncrono ou assíncrono, e estes dois tipos de comunicação
serial podem ocorrer em canais de comunicação simplex, half-duplex e
full-duplex.

Um canal simplex só permite a transmissão de dados, síncrona ou


assíncrona, em uma direção. Um canal half-duplex permite a
comunicação em ambas direções, mas não simultaneamente. Um canal
full-duplex permite a comunicação em ambas direções ao mesmo tempo.

No I/O seqüencial síncrono, um sinal de clock é enviado pelo transmissor,


ou mantido individualmente pelos dois participantes da comunicação
serial e restaurado pelo receptor a partir dos dados.

A comunicação em geral se dá através de blocos com um número fixo de


bytes (tipicamente algo entre 128 e 1024 bytes). A restauração do clock é
feita usando um padrão de bits específico que fica sendo enviado
continuamente quando o canal de comunicação está livre. Um padrão
muito usado é 10000001.

No fim de um bloco, usualmente um ou dois bytes são usados para


conferir se o bloco foi recebido corretamente. Estes bytes contêm a soma
sem vai um de todos os bytes do bloco, o que pode ser usado pelo o
receptor para conferir se recebeu os dados corretamente. Esta técnica é
chamada de checksum do tipo modular sum, mas existem outras.

No caso de não ter recebido um bloco corretamente, o receptor pode se


tornar o transmissor e enviar uma mensagem pedindo o bloco novamente.
Placas de rede em geral usam comunicação serial síncrona.

Na comunicação assíncrona não existe um sinal de clock. Para permitir a


sincronização, cada unidade de dados (pode ser igual 7, ou 8 bits) começa
com um start bit e termina com um ou dois stop bits. O start bit tem
polaridade contrária à dos stop bits.

O canal é reconhecido como vazio pelo receptor pela presença de um


nível lógico contínuo igual ao dos stop bits, que em geral tem o valor
lógico 1.

aula25.doc 11 de 12
Quando aparece um nível lógico igual a zero no canal, o receptor sabe
que o transmissor está enviando um start bit. Assim, logo após o start bit,
o transmissor envia e o receptor recebe os bits de dados e depois os stop
bits, a partir dos quais pode vir um novo start bit, dados, stop bits, etc,
continuamente.

Um código de paridade de um bit de tamanho pode também ser enviado


entre o byte e os stop bits para melhorar a confiabilidade da comunicação
serial assíncrona.

Em um código de paridade ímpar, o bit de paridade é escolhido de forma


que o número de bits 1 transmitidos, incluindo o bit de paridade, é um
número ímpar. Em um código de paridade par, a quantidade de bits 1
transmitidos, incluindo o bit de paridade, é um número par.

Exemplos de comunicação serial assíncrona incluem os padrões RS232,


RS422.

aula25.doc 12 de 12

Você também pode gostar