Anda di halaman 1dari 157

Introdução à Arquitetura

de Computadores
Representação de Dados
Arquitetura e Organização
Noções de Software Básico

Notas de aula

Raul Fernando Weber

Quarta versão - Março de 1998


Material para uso exclusivo em INF01 107
Departamento de Informática Aplicada
Instituto de Informática
UFRGS
ii
PREFÁCIO

Essas notas de aula são uma compilação dos tópicos desenvolvidos na


disciplina de Introdução à Arquitetura de Computadores. Este material foi
desenvolvido para suprir a falta de um livro-texto que contivesse todos os
itens que são abordados na disciplina.

As principais áreas da disciplina são cobertas. Os capítulos 1 e 2 tratam da


representação de dados, cobrindo sistemas de numeração e representação de
dados numéricos em um computador. Os capítulos 3 e 4 enfocam as noções
básicas sobre arquiteturas de computadores, apresentando o modelo de von
Neumann e exercitando os principais conceitos através de um computador
hipotético, denominado de NEANDER. O capítulo 5 introduz um segundo
computador hipotético, o AHMES, dotado de uma maior número de
instruções a fim de exercitar os algoritmos de multiplicação e divisão vistos
no capítulo 6. Os capítulos 7 e 8 cobrem as notações aritméticas de ponto
fixo e ponto flutuante, assim como sistemas de codificação. O capítulo 9
apresenta os blocos básicos da organização de um computador, e o capítulo
10 ilustra a organização do NEANDER. Os capítulos 11 e 12 fornecem as
principais noções e a nomenclatura básica da área de entrada e saída e
software básico (compiladores, montadores e sistemas operacionais).

Enquanto a parte concernente à aritmética binária (capítulos 1, 2, 6, 7 e 8) é


completa em si mesmo, as outras visam somente introduzir o aluno nas áreas
de arquitetura e sistemas operacionais, e estão longe de estarem completas.
Estas demais áreas são posteriormente desenvolvidas em outras disciplinas
do Instituto de Informática.

iii
SOBRE O AUTOR

Raul Fernando Weber


Doutor em Informática pela Universidade de Karlsruhe (1986), Mestre em Ciência da
Computação, UFRGS (1980). Engenheiro Eletrônica, UFRGS (1976). Atualmente
professor do Instituto de Informática e do Curso de Pós-Graduação em Ciência da
Computação, UFRGS. Áreas de atuação: tolerância a falhas, segurança em sistemas de
computação e projeto automatizado de sistemas digitais.

Colaboradores
Taisy Silva Weber
Carlos Arthur Lang Lisboa
Ingrid E.S. Jansch-Porto

iv
SUMÁRIO

1 Bases Numéricas
1.1 Introdução........................................................................................1-1
1.2 Representação de números.....................................................................1-2
1.3 Transformação entre bases.....................................................................1-2
1.3.1 Método polinomial............................................................................1-3
1.3.2 Método de subtrações ........................................................................1-3
1.3.3 Método das divisões..........................................................................1-4
1.3.4 Método da substituição direta................................................................1-5
1.4 Exercícios propostos............................................................................1-5

2 Sistemas de numeração em computação


2.1 Introdução........................................................................................2-1
2.2 Soma de números binários.....................................................................2-2
2.3 Representação de números.....................................................................2-2
2.3.1 Números inteiros positivos..................................................................2-3
2.3.2 Números com sinal: representação em sinal-magnitude..................................2-3
2.3.3 Números com sinal: representação em complemento de (B–1)..........................2-4
2.3.4 Números com sinal: representação em complemento de B...............................2-9
2.4 Comparação entre os métodos...............................................................2-12
2.5 Subtração.......................................................................................2-13
2.6 Estouro de representação.....................................................................2-13
2.7 Exercícios propostos..........................................................................2-14

3 Componentes do computador e modelo de von Neumann


3.1 Breve histórico...................................................................................3-1
3.2 Princípios básicos...............................................................................3-3
3.3 Elementos funcionais básicos..................................................................3-4
3.3.1 Memória........................................................................................3-5
3.3.2 Unidade operacional..........................................................................3-6
3.3.3 Unidade de controle ..........................................................................3-7
3.3.4 Registradores especiais.......................................................................3-8
3.3.5 Conjunto de instruções e modos de endereçamento ......................................3-9
3.3.6 Ciclo de busca-decodificação-execução de instruções....................................3-9
3.3.7 Programação de um processador..........................................................3-10
3.4 Um computador de primeira geração: o EDVAC..........................................3-10
3.5 Modelo de von Neumann: o computador IAS .............................................3-15
3.5.1 Organização da UCP .......................................................................3-15
3.5.2 Conjunto de instruções.....................................................................3-15
3.6 Arquiteturas de 4, 3, 2, 1 e 0 endereços....................................................3-17
3.6.1 Arquitetura de 4 endereços.................................................................3-17
3.6.2 Arquitetura de 3 endereços.................................................................3-18
3.6.3 Arquitetura de 2 endereços.................................................................3-18
3.6.4 Arquitetura de um endereço................................................................3-19
3.6.5 Arquitetura de zero endereços.............................................................3-20

4 Computador hipotético NEANDER


4.1 Características....................................................................................4-1
4.2 Modos de endereçamento.......................................................................4-1
4.3 Conjunto de instruções .........................................................................4-2
4.4 Códigos de condição............................................................................4-2

v
4.5 Formato das instruções.........................................................................4-3
4.6 Exemplo de programação.......................................................................4-3
4.7 Conclusão........................................................................................4-4
4.8 Exercícios de programação usando o NEANDER...........................................4-4

5 Computador hipotético AHMES


5.1 Características....................................................................................5-1
5.2 Modos de endereçamento.......................................................................5-1
5.3 Conjunto de instruções .........................................................................5-2
5.4 Códigos de condição............................................................................5-3
5.5 Manipulação aritmética..........................................................................5-4
5.5.1 Aritmética em complemento de dois........................................................5-4
5.5.2 Aritmética de inteiros positivos..............................................................5-6
5.5.3 Aritmética em complemento de um .........................................................5-6
5.5.4 Aritmética em sinal/magnitude...............................................................5-7

6 Multiplicação e divisão
6.1 Multiplicação binária (números inteiros positivos)..........................................6-1
6.2 Multiplicação binária (números em complemento de dois).................................6-7
6.3 Divisão binária (números inteiros positivos).................................................6-8
6.4 Divisão binária (números em complemento de dois, positivos) .........................6-14
6.5 Divisão binária (números em complemento de dois, positivos ou negativos)..........6-18
6.6 Exercícios resolvidos .........................................................................6-19

7 Números em ponto fixo e ponto flutuante


7.1 Números em ponto fixo ........................................................................7-1
7.2 Soma e subtração em ponto fixo...............................................................7-2
7.3 Multiplicação em ponto fixo....................................................................7-3
7.4 Divisão em ponto fixo ..........................................................................7-4
7.5 Números em ponto flutuante...................................................................7-4
7.6 Formatos de números em ponto flutuante....................................................7-6
7.7 Soma e subtração de números em ponto flutuante...........................................7-7
7.8 Multiplicação de números em ponto flutuante................................................7-7
7.9 Divisão de números em ponto flutuante ......................................................7-8

8 Codificações BCD, Numérica e Alfanumérica


8.1 Números e Aritmética BCD....................................................................8-1
8.2 Codificação.......................................................................................8-3
8.3 Códigos BCD (ou códigos de 4 bits ponderados)...........................................8-3
8.4 Códigos de cinco bits ponderados.............................................................8-5
8.5 Códigos de sete bits ponderados ..............................................................8-5
8.6 Códigos Gray (ou códigos cíclicos)...........................................................8-6
8.7 Códigos de detecção e correção de erros .....................................................8-8
8.8 Códigos m-de-n .................................................................................8-9
8.9 Códigos de paridade ............................................................................8-9
8.10 Códigos de Hamming.........................................................................8-9
8.11 Códigos alfabéticos (ou códigos alfanuméricos).........................................8-11

9 Elementos básicos de organização


9.1 Introdução........................................................................................9-1
9.2 Portas lógicas e equações booleanas..........................................................9-1
9.3 Equivalência de portas lógicas.................................................................9-5
9.4 Circuitos combinacionais.......................................................................9-7
9.5 Circuitos sequenciais..........................................................................9-10
9.6 Unidade Aritmética e Lógica.................................................................9-15
9.7 Memória........................................................................................9-18

10 Organização do Neander
10.1 Elementos necessários.......................................................................10-1

vi
10.2 Fluxo de dados...............................................................................10-1
10.3 Sinais de controle............................................................................10-7

11 Entrada e saída
11.1 Introdução ....................................................................................11-1
11.2 Dispositivos periféricos.....................................................................11-2
11.3 Memória secundária .........................................................................11-2
11.4 Comunicação com outras máquinas .......................................................11-2
11.5 Sistemas de E/S..............................................................................11-3
11.5.1 Entrada e saída programada..............................................................11-3
11.5.2 Acesso direto à memória..................................................................11-5
11.5.3 Interrupção.................................................................................11-5
11.5.4 Informações complementares............................................................11-5

12 Software básico
12.1 Introdução ....................................................................................12-1
12.2 Linguagens de programação................................................................12-1
12.3 Exemplo com NEANDER..................................................................12-2
12.4 Programas a nível de sistema...............................................................12-5
12.5 Interfaces entre hardware e software ......................................................12-7
12.6 Sistemas operacionais.......................................................................12-8
12.7 Funções básicas dos sistemas operacionais..............................................12-11
12.8 Processos e escalonamento................................................................12-12
12.9 Carga do sistema (inicialização da máquina).............................................12-13
12.10 Multiprogramação.........................................................................12-13
12.11 Multiprocessamento.......................................................................12-15
12.12 Exemplos de sistemas operacionais.....................................................12-15
12.13 Redes de computadores...................................................................12-16

Bibliografia

Apêndice: Utilização dos simuladores e depuradores


A.1 Simulador.......................................................................................A-1
A.2 Formato da tela.................................................................................A-1
A.3 Entrada numérica...............................................................................A-2
A.4 Comandos de operação........................................................................A-2
A.4.1 Hexadecimal x decimal .....................................................................A-3
A.4.2 Visualização Simbólica .....................................................................A-3
A.4.3 Editando um programa na memória.......................................................A-3
A.4.4 Inspecionando a memória ..................................................................A-4
A.4.5 Imprimindo porções da memória..........................................................A-4
A.4.6 Zerando uma área de memória.............................................................A-4
A.4.7 Movendo blocos.............................................................................A-4
A.4.8 Executando um programa ..................................................................A-4
A.4.9 Salvando e carregando arquivos...........................................................A-5

vii
Capítulo
UM
Bases Numéricas

1 . 1 Introdução

Quando o homem aprendeu a contar, ele foi obrigado a desenvolver símbolos que
representassem as quantidades e grandezas que ele queria utilizar. Estes símbolos, os
algarismos, constituem a base dos sistemas de numeração.

Nos tempos pré-históricos o homem utilizou uma correspondência um-para-um entre os


objetos a serem contados e os seus dedos, ou então para pedrinhas ou mesmo para “riscos”.
Um sistema deste tipo seria um “sistema unário” (com um único símbolo):

Os primeiros algarismos encontrados consistiam em marcas horizontais ou verticais (como as


acima) com traços de ligação entre elas para definir as quantidades superiores a um. Os
símbolos romanos básicos podem ser considerados como uma evolução do sistema acima.
Os algarismos romanos são basicamente aditivos: assim, por exemplo, III = I + I + I. Para
facilitar a representação de grandes quantidades, foram introduzidos símbolos especiais para
grupos:

I=1 V=5 X=10 L=50 C=100 D=500 M=1000


Além disto, utilizam-se uma série de regras (como por exemplo a posição relativa dos
símbolos aos seus vizinhos), que permitiam interpretar estes símbolos e determinar qual o
número que estava sendo representado:

VI=5+1=6 CXVI=100+10+5+1=116
IV=5-1=4 MCMLIX=1000+(1000-100)+50+(10-1)=1959

A realização de cálculos com este sistema, especialmente para operações como multiplicação
e divisão, era entretanto extremamente complexa e de aplicação praticamente impossível.
Posteriormente, os árabes utilizaram-se de um sistema originário da Índia, que possuía 10
símbolos (0 a 9), com os seguintes símbolos (da esquerda para direita, 1234567890):

Este sistema começou a ser utilizado na Europa no século 12, e é conhecido atualmente como
sistema de numeração arábica (mas com outros algarismos), e se destaca pelas seguintes
características:

• existe um símbolo para o valor nulo.


• cada algarismo utilizado é uma unidade maior que o seu predecessor.

1-1
• a notação é posicional, ou seja, o valor de um algarismo é determinado pela sua
posição dentro do número. Cada posição possui um determinado peso.

1 . 2 Representação de números

Os sistemas atuais formam os números pela fórmula a seguir, onde a representa o número
propriamente dito, B representa a base do sistema de numeração (B≥2), x i representam os
algarismos (0≤xi<B), e n representa o número de posições utilizadas. Com B=10 tem-se o
sistema decimal.
n

a= Σ (xi.B i)
i=–m

O algarismo xi tem peso Bi, determinado pela sua posição. Para i com valores positivos,
tem-se pesos maiores que a unidade; para i=0 tem-se exatamente o peso unitário (B0=1).
Para valores negativos de i, tem-se pesos menores que a unidade (fracionários). Para o caso
específico de números inteiros, utilizando-se n dígitos (ou “casas”), indexados de 0 até n–1,
a fórmula fica:
n-1

a= Σ (xi.B i)
i=0

ou, por extenso, expandindo-se o somatório:


a =xn-1.B n-1+xn-2.B n-2+. . . . +x 2.B 2+x1.B 1+x0.B 0

ou ainda, simplificando-se a expressão:


a =xn-1.B n-1+xn-2.B n-2+. . . . +x 2.B 2+x1.B+x0

Para os sistemas de numeração utilizam-se as seguintes regras:


• A base B de um sistema é igual à quantidade de algarismos distintos utilizados. Para a
base decimal, tem-se 10 algarismos distintos (de 0 a 9).

• Quando uma posição é ocupada pelo maior algarismo, e ela deve ser aumentada de uma
unidade, então esta posição recebe o símbolo nulo e a posição seguinte deve ser
aumentada de uma unidade. Assim, 9+1=10, 19+1=20, 99+1=100, 1999+1=2000.

• O algarismo mais a direita (denominado de dígito menos significativo) tem peso um. O
algarismo imediatamente a esquerda tem o peso da base B, o seguinte a esquerda tem
peso de B ao quadrado, depois B ao cubo, e assim por diante.

• O valor de cada algarismo de um número é determinado multiplicando-se o algarismo


pelo peso de sua posição.
• O valor de um número é determinado pela soma dos valores de cada algarismo.

1 . 3 Transformação entre bases


Os computadores atuais utilizam internamente somente um sistema de numeração: o sistema
binário (B=2), com os algarismos 0 e 1. Este sistema foi estudado profundamente pelo
matemático alemão Leibniz, no século 17. Somente com o advento dos computadores
eletrônicos digitais, entretanto, tal sistema começou a ser utilizado na prática. Atualmente,

1-2
todos os computadores utilizam internamente o sistema binário para armazenamento e
manipulação de números e dados. O tratamento de números representados em outras bases
ocorre através de rotinas de codificação e decodificação. O mesmo ocorre com símbolos
alfanuméricos.

1 . 3 . 1 Método polinomial

Como cada número pode ser representado por um polinômio em uma certa base, tudo o que
se deve fazer para transformar um número de uma base para outra é interpretar este número
como um polinômio utilizando-se aritmética da base de destino:

1100012=1.25+1.24+0.23+0.22+0.21+1.20=32+16+0+0+0+1=4910

1100012=1.25+1.24+0.23+0.22+0.21+1.20=408+208+0+0+0+1 8=618

Nos exemplos acima, simplesmente aplicou-se a fórmula:

a = xn-1.B n-1+xn-2.B n-2+. . . . +x 2.B 2+x1.B+x0

onde B é a base de origem e a é o número resultante na base destino. Observe-se que todos
os cálculos são realizados na aritmética da base de destino.

1 . 3 . 2 Método de subtrações
Sabendo-se que um número em uma determinada base B é representado pela fórmula

a =xn-1.B n-1+xn-2.B n-2+. . . . +x 2.B 2+x1.B+x0

a conversão para determinação dos coeficientes xi é iniciada da esquerda (xn-1) para a direita
(até x0). Inicia-se determinando os valores de n (a quantidade de dígitos necessária) e de xn-1
(o dígito mais significativo). Para tanto procura-se o produto (na base origem) do maior
coeficiente com a maior potência da nova base, que está contido no número a ser convertido,
ou seja, procura-se o maior produto xn-1.B n-1 que seja menor (ou igual) que a. Este
coeficiente xn-1 é o algarismo a ser utilizado na posição mais à esquerda (dígito mais
significativo) do número na nova base. Subtrai-se este produto do número a ser convertido.
Com isto tem-se:

a ' = a – xn-1.B n-1 = xn-2.B n-2+. . . . +x 2.B 2+x1.B+x0

Para determinar-se o algarismo seguinte à direita (xn-2), repete-se o processo, usando agora a
diferença do passo anterior (a') e a potência imediatamente inferior (se no passo anterior
usou-se a potência Bi, utiliza-se agora Bi-1), e assim sucessivamente até todos os n dígitos
terem sido determinados. Note-se que o resultado das diversas subtrações sempre deve ser
positivo (ou zero). Se a subtração não for possível, isto indica que o coeficiente xi é zero.

681-1.29=681-512=169 169-0.28=169-0.256=169
169-1.27=169-128=41 41-0.26=41-0.64=41
41-1.25=41-32=9 9-0.24=9-0.16=9
9-1.23=9-8=1 1-0.22=1-0.4=1
1-0.21=1-0.2=1 1-1.20=1-1=0

Ou seja, o número final, em binário, é 1010101001.

1-3
Se o resultado de uma subtração produzir resultado zero, isto significa que todos os dígitos
restantes são zero, como ilustrado no exemplo a seguir.

680-1.29=680-512=168 168-0.28=168-0.256=168
168-1.27=168-128=40 40-0.26=40-0.64=40
40-1.25=40-32=8 8-0.24=8-0.16=8
8-1.23=8-8=0

Os coeficientes restantes (x2, x 1 e x 0) são iguais a zeros, e o número final, em binário, é


1010101000.

O método também se aplica para números com frações. Se não for possível chegar a zero
após um certo número de posições, então interrompe-se o método após o número de casas
desejado.

6,125-1.22=6,125-4=2,125 2,125-1.21=2,125-2=0,125
0,125-0.20=0,125-0.1=0,125 0,125-0.2-1=0,125-0.0,5=0,125
0,125-0.2-2=0,125-0.0,25=0,125 0,125-1.2-3=0,125-0,125=0

Ou seja, o número final é 110,001.


O método também se aplica para outras bases. Seja a conversão de 6,8125 de decimal para
octal:

6,8125-6.80=6,8125-6=0,8125 0,8125-6.8-1=0,8125-0,7500=0,0625
0,0625-4.8-2=0,0625-0,0625=0

Ou seja, 6,8125 10=6,648. Note-se que sempre se utiliza a aritmética da base de origem.

1 . 3 . 3 Método das divisões


O número a ser convertido é dividido pela nova base (na aritmética da base de origem). O
resto desta divisão forma o algarismo mais a direita (menos significativo) do número
convertido. O quociente é novamente dividido, e assim sucessivamente, até o quociente final
ser zero. A sequência de todos os restos forma o novo número.
Note-se que ao dividir o número a pela base B obtém-se:

a /B=(xn-1.B n-1+xn-2.B n-2+. . . . +x 2.B 2+x1.B+x0)/B


ou seja
a /B=xn-1.B n-2+xn-2.B n-3+. . . . +x 2.B 1+x1, com resto igual a x0

A divisão seguinte por B produz como resto x1, e assim sucessivamente até xn-1.

53÷2=26, resta 1 26÷2=13, resta 0


13÷2=6, resta 1 6÷2=3, resta 0
3÷2=1, resta 1 1÷2=0, resta 1
Número binário resultante: 110101
Para frações, o método se modifica um pouco: a fração é multiplicada pela nova base; a parte
inteira resultante forma o algarismo mais à esquerda da nova fração e a parte fracionária é
submetida novamente ao método, até o resultado ser zero (ou até atingir-se o número de
dígitos significativos desejado).

1-4
Exemplo:
0,828125 . 2 = 1,65625 Parte inteira = 1 Fração = 0,1
0,65625 . 2 = 1,3125 Parte inteira = 1 Fração = 0,11
0,3125 . 2 = 0,625 Parte inteira = 0 Fração = 0,110
0,625 . 2 = 1,25 Parte inteira = 1 Fração = 0,1101
0,25 . 2 = 0,5 Parte inteira = 0 Fração = 0,11010
0,5 . 2 = 1,0 Parte inteira = 1 Fração = 0,110101

1 . 3 . 4 Método da substituição direta

Este é o método mais fácil, entretanto funciona somente para bases que são potências inteiras
entre si, como por exemplo de octal para binário (e vice-versa) ou de hexadecimal para
binário (e vice-versa). Seja B1=B2m; para um determinado m inteiro, então tem-se as
seguintes regras:
• para converter de B1 (a maior base) para B2 (a menor base), cada algarismo de B1 é
substituído por m algarismos equivalentes de B2:

5 1 78 = 101 001 1112


7 0 C16 = 0111 0000 11002

• para converter de B2 para B1, agrupam-se os algarismos em grupos de m, tomando-se


a vírgula como referência, ou seja, formam-se grupos de m algarismos tanto para a
esquerda da vírgula (parte inteira) como para a direita (parte fracionária). Cada grupo é
então transformado no seu algarismo equivalente na nova base:
1110,01101 2 = 001 110 , 011 010 = 1 6 , 3 28
1110,01101 2 = 1110 , 0110 1000 = E , 6 816

Observe-se que este método também pode ser utilizado entre duas bases que não seja
diretamente uma potência da outra, desde que ambas sejam potências inteiras de uma terceira
base. Assim, por exemplo, pode-se converter da base octal para hexadecimal (usando-se a
base 2 como terceira base).

1 . 4 Exercícios propostos
1. Converter para a base decimal os seguintes números:

a) 1010102
b)10103
c) 10214
d) 10256
e) 21658
f) 1FA216
g) E1A16
h) 7078

2. Usando o método das divisões, converter os seguintes números decimais para a base
indicada:
a) 96 para a base ternária
b) 96 para a base octal
c) 258 para a base hexadecimal
d) 258 para a base binária
e) 49 para a base quaternária
f) 57 para a base ternária

1-5
g) 56 para a base binária
3. Usando o método das subtrações, converter os seguintes números decimais para a base
indicada:
a) 96 para a base ternária
b) 96 para a base octal
c) 258 para a base hexadecimal
d) 258 para a base binária
e) 49 para a base quaternária
f) 57 para a base ternária
g) 56 para a base binária
4. Usando o método das substituições, converter os seguintes números para a base indicada:
a) 1011000110102 para a base octal
b) 1011000110102 para a base hexadecimal
c) 001011001012 para a base octal
d) 001011001012 para a base hexadecimal
e) 3478 para a base binária
f) 72418 para a base binária
g) 3AF16 para a base binária
h) 7E4B16 para a base binária

1-6
Capítulo
DOIS
Sistemas de numeração em computação

2 . 1 Introdução

Em todas as fórmulas usadas a seguir, ‘B’ representa a base do sistema de numeração, ‘n’
representa a quantidade de dígitos disponíveis para representar os números, e ‘a’, ‘b’ e ‘c’
representam números. A fórmula utilizada para representar um número inteiro:
n-1

a= Σ (xi.B i)
i=0
será representada por a=Σ n-1xiBi, ficando a variação de i desde 0 até o limite (n-1) implícita.

Para uma determinada base B, empregando-se n dígitos, pode-se representar Bn combi-


nações distintas, ou seja, Bn números distintos. Assim, para base decimal com três dígitos
pode-se representar 1000 números distintos (com zero incluído!). Entretanto, com os
mesmos três dígitos e base dois, representa-se somente 8 números distintos. Assim,
números binários vão exigir um grande número de dígitos, e normalmente trabalha-se com
grandes cadeias de zeros e uns. Isto pode levar a erros visuais, e por isso empregam-se
comumente as notações em base 8 e base 16 para representar números binários.

A tabela abaixo lista os primeiros 16 números em binário, decimal, octal e hexadecimal.


Binário Decimal Octal Hexadecimal
0000 0 00 0
0001 1 01 1
0010 2 02 2
0011 3 03 3
0100 4 04 4
0101 5 05 5
0110 6 06 6
0111 7 07 7
1000 8 10 8
1001 9 11 9
1010 10 12 A
1011 11 13 B
1100 12 14 C
1101 13 15 D
1110 14 16 E
1111 15 17 F
Tabela 2.1 - Números em binário, decimal, octal e hexadecimal

Em computação trabalha-se normalmente com quatro bases: a decimal, para entrada e saída
dos dados (já que nossa sociedade é baseada no sistema decimal); a binária, para os cálculos
internos; a hexadecimal, como forma compactada de representação interna; e a octal, também
por este motivo. Note-se que a escolha das bases 8 e 16 não é ocasional: as transformações

2-1
entres as bases 2, 8 e 16 pode ser feita facilmente pelo método da substituição direta. Embora
a base hexadecimal seja de representação mais complexa (utiliza letras e dígitos), ela é
preferida sobre a base octal por ser mais compacta, ou seja, requerer menos espaço para
representar os resultados.

Os números do sistema binário são formados como qualquer outro número do sistema de
numeração arábico (inclusive em octal ou hexadecimal): cada novo número é obtido por
enumeração, somando-se um ao seu antecessor (e observando-se a regra do “vai-um”).

Cada dígito do sistema binário (0 e 1) é denominado de bit, a contração de binary digit. A


determinados conjuntos de bits são empregados nomes específicos. Assim, um quarteto (4
bits) é frequentemente denominado de nibble, e um octeto (8 bits) recebe a denominação de
byte (ou o termo aportuguesado baite). Os múltiplos deste conjuntos utilizam os mesmos
denominadores que no sistema decimal (K para kilo, M para Mega, G para giga, T para
3 10
Tera, P para Peta), mas o fator multiplicativo não é 1000 (10 ) mas sim 1024 (2 ). Assim,
um kilobit (abreviado 1Kb) são 1024 bits, e um kilobyte (abreviado 1KB) são 1024 bytes.
Um megabyte (1MB) são 1024 KB; um gigabyte (1GB) são 1024 MB, um terabyte (1TB)
são 1024 GB e assim por diante.

2 . 2 Soma de números binários


A soma de dois números binários utiliza as mesmas regras da soma no sistema decimal.
Como existem entretanto somente dois símbolos, a tabela de soma é extremamente simples:

a c d=a+c
0 0 0
0 1 1
1 0 1
1 1 0 e “vai-um”
Tabela 2.2 - Tabela verdade de um meio-somador (half adder)

Observe-se que soma de 1 mais 1 resulta em 10 (dois), ou seja, o dígito do resultado é zero e
existe a ocorrência de um “vai-um” (carry out, em inglês). Considerando-se a possível
existência de um “vem-um” (carry in) e de “vai-um” (carry out), obtém-se a tabela a seguir.

a c vem-um d=a+c vai-um


0 0 0 0 0
0 0 1 1 0
0 1 0 1 0
0 1 1 0 1
1 0 0 1 0
1 0 1 0 1
1 1 0 0 1
1 1 1 1 1
Tabela 2.3 - Tabela verdade de um somador completo (full adder)

2 . 3 Representação de números
A representação de números inteiros positivos é direta e imediata. Entretanto, é necessário
expandir (ou modificar) esta representação para incluir também números negativos. Diversas
representações foram desenvolvidas com este propósito. Quatro destas representações, as
mais comuns atualmente, são analisadas a seguir: inteiros positivos, sinal/magnitude,
complemento de B-1 e complemento de B.

2-2
2 . 3 . 1 Números inteiros positivos
Considerando-se somente a representação de números inteiros positivos, com n dígitos
pode-se representar Bn números, dispostos no intervalo fechado entre zero (o menor) e Bn–1
(o maior).

Faixa de representação: [ 0 , Bn–1 ]

Assim, para 2 dígitos decimais tem-se 100 números, de 0 a 99; para 3 dígitos em base 3 tem-
se 27 números, de 0 (0003) a 26 (222 3); para 4 dígitos binários tem-se 24=16 números,
desde zero (00002) até 15 (11112); para 8 dígitos binários tem-se 28=256 números, desde
zero (000000002) até 255 (111111112); Não existe previsão para números negativos.

Cálculo do valor do número

O valor de número positivo é calculado pelo polinômio Σ n-1xiBi, exatamente o mesmo


método utilizado para a conversão pelo método polinomial.

Troca de sinal
Como não existe a capacidade de representar números negativos, não existe tal função.
Soma de dois números

A soma de dois números positivos é realizada somando-se os dois polinômios dígito a


dígito, de acordo com as tabelas da soma na base em questão. Para base 2 utiliza-se a Tabela
2.3 da seção 2.2. Naturalmente, nesta soma leva-se em conta os eventuais “vai-um” e “vem-
um”.

2 . 3 . 2 Números com sinal: representação em sinal-magnitude

Para permitir números com sinal, esta representação utiliza um dígito (normalmente o mais
significativo) para representar o sinal. No sistema decimal, o símbolo ‘–’ é usado para
indicar números negativos e ‘+’ (ou simplesmente um espaço vazio) para números positivos.

Com isto ganha-se a possibilidade de representar inteiros negativos, mas a faixa de


representação é reduzida porque tem-se agora somente (n–1) dígitos para representar a
magnitude.

Faixa de representação: [ –(Bn-1–1) , +(Bn-1–1) ] ou [ –(Bn-1–1) , (Bn-1–1) ]

Divide-se a gama de representação em dois subconjuntos simétricos, um para números


negativos e outro para positivos. A faixa dos números positivos é reduzida de um fator igual
a base B. Por exemplo, para base decimal com dois dígitos, se consideramos somente
números positivos tem-se a faixa de 0 até 99; com a representação em sinal magnitude
obtém-se a faixa de –9 até +9. Além disto, existem duas representações para o zero: –0 e +0.
De um modo geral, para uma base qualquer, das Bn combinações possíveis usam-se somente
2.B n-1–1 (descontando-se o duplo zero). No exemplo acima, das 100 combinações são
utilizadas somente 19.

Em binário, entretanto, com B=2, obtém-se 2.2n-1–1, ou seja, 2n–1 (isto é, Bn–1). Assim,
somente uma representação é perdida (a do duplo zero). Obs.: no sistema binário costuma-se
utilizar ‘1’ no lugar de ‘–’, e ‘0’ no lugar de ‘+’. Assim, para 4 dígitos, 0111 representa 7,
1111 representa –7, 0000 representa zero e 1000 representa –0.

2-3
Cálculo do valor do número
Um número em sinal magnitude, independente de qual a base utilizada, é formado por duas
parcelas, escritas lado a lado. A parcela à esquerda (S(a)) representa o sinal e a parcela à
direita (M(a)) a magnitude:

a = S(a)M(a)

onde S(a) é ‘+’ ou ‘–’, e M(a)=Σ n-2xiBi. Em binário, utiliza-se usualmente ‘0’ no lugar de
‘+’ e ‘1’ ao invés de ‘-’.

Troca de sinal
Para trocar o sinal de um número ‘a’ em sinal magnitude, troca-se simplesmente S(a),
mantendo-se a magnitude M(a). Assim, no caso de c=–a, tem-se M(c)=M(a), e se S(a)=‘+’,
então S(c)=‘–’, e se S(a)=‘–’ então S(c)=‘+’.

Soma de dois números


A soma de dois números em sinal magnitude é realizada de acordo com as regras da tabela
abaixo. No caso, deseja-se calcular d=a+c.

S(a) S(c) S(d) M(d) Exemplo


+ + + M(a)+M(c) 5 + 7 = 12
– – – M(a)+M(c) -5 + -7 = -12
+ – se M(a)≥M(c), + M(a)–M(c) 7 + -5 = 2
se M(a)<M(c), – M(c)–M(a) 5 + -7 = -2
– + se M(a)>M(c), – M(a)–M(c) -7 + 5 = -2
se M(a)≤M(c), + M(c)–M(a) -5 + 7 = 2
Tabela 2.4 - Soma em sinal / magnitude

Note-se que, para realizar somas de números em sinal/magnitude, deve-se conhecer as


tabelas de soma e de subtração da base em que se estiver trabalhando. Esta necessidade de
realizar duas operações distintas (soma e subtração) torna muito mais complexa a
manipulação de números em sinal/magnitude pelo computador. Assim, apesar de familiar ao
ser humano, esta notação é pouco utilizada em computação.

Para resolver o problema de representação de números negativos, optou-se por usar a


notação em complemento da base, como visto a seguir. Duas notações foram desenvolvidas:
em complemento de (B-1) e em complemento de B Observação: a tabela de subtração em
binário será vista mais adiante (seção 2.5).

2 . 3 . 3 Números com sinal: representação em complemento de (B–1)

Para permitir que a operação de soma seja realizada de forma única, sem preocupação com os
sinais dos operandos, é utilizada a representação em complemento. Números positivos são
representados na forma normal, e números negativos são representados em complemento.
Na representação em complemento de (B-1), o complemento de um número a é obtido
subtraindo-se este número da maior quantidade representável, ou seja, Bn–1–a. Assim, na
base 10, com 3 dígitos, o complemento de 9 (10-1) de um número a é obtido pela fórmula
999-a. Note-se que isto equivale a subtrair cada um dos dígitos de a de 9, que é justamente a
base 10 menos um. Generalizando, para representar números negativos em complemento de
B–1, cada algarismo xi é complementado individualmente pela fórmula B–1–xi. A tabela a
seguir ilustra o cálculo do complemento para diversas bases.

2-4
Algarismo B=2 B=3 B=4 B=8 B=9 B=10
0 1 2 3 7 8 9
1 0 1 2 6 7 8
2 - 0 1 5 6 7
3 - - 0 4 5 6
4 - - - 3 4 5
5 - - - 2 3 4
6 - - - 1 2 3
7 - - - 0 1 2
8 - - - - 0 1
9 - - - - - 0
Tabela 2.5 - Exemplos de cálculo de complemento de B-1

A notação em complemento elimina a necessidade de representação explícita do símbolo do


sinal, ou seja, não existe um símbolo especial para o sinal. A faixa de números
representáveis é dividida em duas. Uma das faixas representa números positivos, e a outra
faixa representa números negativos. Existindo m representações, as primeiras m/2 são
utilizadas para números positivos (de 0 a m/2), e as m/2 representações seguintes (de m/2 até
m) são utilizadas para números negativos. A faixa de representação fica:

Faixa de representação, para B par: [ –(Bn/2–1) , +(B n/2–1) ]


Faixa de representação, para B ímpar: [ –(Bn–3)/2 , +(B n–1)/2 ]
A tabela a seguir ilustra as faixas para diversas bases. Note-se que a gama de representação é
dividida em dois subconjuntos, um para números negativos e outro para positivos. A faixa
dos números positivos é reduzida da metade (em relação aos inteiros positivos). Continua
existindo a dupla representação do zero, assim como em sinal magnitude, mas não existe
mais a perda de capacidade de representação devido a existência do dígito de sinal.

Base Num.dig. Faixa Faixa em decimal


2 4 1000,1001,..,1111,0000,0001,..,0111 –7,–6,..,–0,+0,1,..7
3 3 112,120,121,..,222,000,001,..,111 –12,–11,–10,..,–0,+0,1,..,13
4 3 200,201,..,333,000,001,..,132,133 –31,–30,..,–0,+0,1,..31
8 3 400,401,..,777,000,001,..,376,377 –255,–254,..,–0,+0,..,255
9 2 45,46,..,88,00,01,..,43,44 –39,–38,..,–0,+0,1,..,39,40
10 2 50,51,..,98,99,00,01,..,48,49 –49,–48,..,–1,–0,+0,..,48,49
Tabela 2.6 - Exemplos de faixas de representação em complemento de B-1

Por exemplo, para base decimal com dois dígitos, se consideramos somente números posi-
tivos tem-se a faixa de 0 até 99; com a representação em complemento de 9 obtém-se a faixa
de 50 a 99 e 0 a 49. A primeira metade (de 50 a 99) representa números negativos (de –49 a
–0); a segunda metade (de 0 a 49) representa números positivos. Note-se que um número
iniciando por 9, 8, 7, 6 ou 5 é negativo; iniciando por 0, 1, 2, 3 ou 4 é positivo. Em binário,
para 4 dígitos, tem-se a faixa 1000 a 1111 (–7 a –0) e 0000 a 0111 (0 a 7). Números
iniciando por 1 são negativos, e iniciando por 0 são positivos.

Para bases ímpares, existe um número positivo a mais, e para a determinação do sinal não
basta a verificação do dígito mais significativo (veja-se a seguir). Por exemplo, na tabela 2.6,
os números em base 3 iniciando por zero são positivos, e os números iniciando por 2 são
negativos, mas dos números iniciando por 1 metade são positivos (100, 101, 102, 110 e
111) e metade são negativos (112, 120, 121 e 122).

2-5
Cálculo do valor do número
Um número em complemento de (B–1) tem o seu valor calculado de acordo com os
seguintes passos:

• determinação do sinal: se a representação do número, interpretado como inteiro


positivo, estiver na metade superior da faixa, ele é negativo. Se estiver na metade
inferior da faixa, é positivo. Ou seja, sendo o número a=Σ n-1xiBi, se a≥ B n/2, então
este número em complemento de (B–1) é negativo. Se a<Bn/2, então, em complemento
de (B–1), ele é positivo. Obs.: para bases pares, basta analisar o dígito mais
significativo, conforme já explicado.

Assim, por exemplo, para base 3 com três dígitos, tem-se 33 = 27 representações. A
metade é 13,5, e assim os números de 0 a treze (representados respectivamente por
000 a 111) são positivos, e os de 14 a 26 (representados por 112 a 222) são negativos.
Note-se que existem 14 números positivos, e 13 números negativos.

Em outro exemplo, para base 4 com três dígitos, tem-se 43 = 64 combinações. A


metade é 32, e então os números de 0 a 31 (representados por 000 a 133) são
positivos, e os de 32 a 63 (representados por 200 a 333) são negativos. Existem neste
caso 32 números positivos e 32 números negativos.
• determinação da magnitude do número. Se o número a for positivo, então sua
magnitude é dada por M(a)=Σ n-1xiBi. Se o número a for negativo, para calcular sua
magnitude (valor absoluto) deve-se calcular o complemento de (B–1) de cada
algarismo, ou seja, substitui-se cada dígito pelo seu complemento. Alternativamente,
também é possível manipular todo o número:
M(a)=Σ n-1(B-1-xi)Bi = Σ n-1(B-1)Bi – Σ n-1xiBi = Bn–1–Σ n-1xiBi =Bn–1–a

Assim, por exemplo, para base 3 com três dígitos, a magnitude de 110 é 12 (o número
é positivo). Já o número 112 é negativo, e assim seus dígitos devem ser
complementados, resultando em 110, ou seja, a magnitude também é 12. Utilizando-se
a fórmula acima, tem-se também 33–1–(112)3 = 27–1–14=26–14=12.

A tabela a seguir fornece diversos exemplos do cálculo do valor de um número, de acordo


com as regras descritas acima.

Base Num.dig. Número Sinal Magnitude Num.decim.


2 4 1110 – 1 –1
2 4 1001 – 6 –6
2 4 1010 – 5 –5
2 4 0101 + 5 +5
3 3 102 + 11 +11
3 3 111 + 13 +13
3 3 121 – 10 –10
10 2 98 – 1 –1
10 2 99 – 0 –0
10 3 45 + 45 +45
10 3 54 – 45 –45
10 3 76 - 23 -23
10 3 50 - 49 -49
Tabela 2.7 - Exemplos de cálculo do valor de um número em complemento de B-1

2-6
Troca de sinal
Para trocar o sinal de um número a em complemento de (B–1), basta complementar, também
em B–1, cada um de seus dígitos. Assim, no caso de c=–(a), tem-se, pelo raciocínio acima,
c=Bn–1–a. Note-se que –(–(a)) = –(Bn–1–a) = Bn–1–(Bn–1–a) = a.
No caso de bases ímpares, o maior positivo, ao ser trocado de sinal, resulta novamente em si
próprio. Nestes casos, diz-se que houve estouro de representação (veja seção 2.6). Para
bases pares, isto nunca ocorre (para complemento de B-1). A tabela abaixo ilustra diversos
casos de troca de sinal.

Base Num.dig. Número Núm.negado Magnitude


2 4 1110 0001 1
2 4 1001 0110 6
2 4 1010 0101 5
2 4 0101 1010 5
3 3 102 120 11
3 3 111 111 (estouro) 13
3 3 121 101 10
10 2 98 01 1
10 2 99 00 0
10 2 45 54 45
10 2 54 45 45
16 2 01 FE 1
16 2 FF 00 0
16 2 98 67 103
Tabela 2.8 - Exemplos de troca de sinal para números em complemento de B-1

Soma de dois números


Para entender-se o processo de soma de dois números em complemento de (B–1), deve-se
analisar os mesmos casos da soma em sinal magnitude. Na Tabela 2.9 a seguir, deseja-se
realizar a soma d=a+c. Os números a e c estão em complemento de (B–1), e quer-se obter d
também em complemento de (B–1). Note-se que um número x, quando for negativo, está
representado por Bn-1-M(x). As quatro primeiras colunas da esquerda da tabela mostram os
mesmos seis casos da soma em sinal/magnitude, e a coluna da direita ilustra o resultado
obtido pela simples soma dos dois operandos, sem analisar-se seus sinais, suas magnitudes
ou se eles devem na realidade ser somados ou subtraídos (ou seja, simplesmente realiza-se a
soma a+c).
Em todos os casos mostrados na Tabela 2.9, usam-se sempre as mesmas tabelas de soma de
números inteiros (não se utilizam tabelas especiais para soma ou subtração em complemento
de (B–1)). Note-se que em três casos, marcados com (*), não se obtém diretamente o
número já representado em complemento de (B–1); existe um termo excedente de Bn–1. Nos
outros três casos, não marcados, o resultado obtido já está correto.

Para reconhecer facilmente os casos que necessitam de correção, basta observar-se que o
termo Bn corresponde a um dígito na posição (n+1). Como os números representados tem
somente n posições, a posição (n+1) só será ocupada naquelas somas que produzirem um
“vai-um”. Assim, se o termo Bn estiver presente, ele só pode ter sido gerado, na soma, por
um “vai-um”. Ou seja, somente os casos que produzirem um “vai-um” são os que
necessitam de correção. Para eliminar o termo Bn, basta eliminar-se este “vai-um”. E para
eliminar o termo (–1), adiciona-se um ao resultado.

2-7
S(a) S(c) S(d) M(d) Resultado obtido pela
simples soma de a+c
+ + + M(a)+M(c) a+c
– – – M(a)+M(c) Bn-1-M(a) + Bn-1-M(c)
Bn-1+Bn-1-(M(a)+M(c))
Bn-1 + Bn-1 - M(d)
Bn-1 + d (*)
+ – se M(a)≥M(c), + M(a)–M(c) M(a) + Bn-1-M(c)
Bn-1 + M(a)-M(c)
Bn-1 + d (*)
se M(a)<M(c), – M(c)–M(a) M(a) + Bn-1-M(c)
Bn-1 - (M(c)-M(a))
Bn - 1 - M(d)
d
– + se M(a)>M(c), – M(a)–M(c) Bn-1 - M(a) + M(c)
Bn-1 - (M(a)-M(c))
Bn-1 - M(d)
d
se M(a)≤M(c), + M(c)–M(a) Bn-1 - M(a) + M(c)
Bn-1 + (M(c)-M(a))
Bn-1 + d (*)
Tabela 2.9 - Soma em complemento de B-1

Simplificando, tem-se a seguinte regra de correção: para obter a soma correta em


complemento de (B–1), basta somar o “vai-um” ao resultado (e eliminar o “vai-um”). Ou,
dito de outra maneira, se ocorrer um “vai-um”, deve-se somar um ao resultado para corrigi-
lo. Se não ocorrer “vai-um”, o resultado já está correto. A tabela a seguir ilustra exemplos de
diversas somas, tanto em base 2 (complemento de um) como em base 10 (complemento de
nove).

Base Num.dig. a c d=a+c d corrigido


2 4 1110 0001 1111 1111
2 4 1111 0001 10000 0001
2 4 1001 0111 10000 0001
2 4 0110 1111 10101 0110
2 4 0101 1000 1101 1101
2 4 0011 0011 0110 0110
2 4 1111 1111 11110 1111
2 4 0001 1110 1111 1111
10 2 98 37 135 36
10 2 99 00 99 99
10 2 99 01 100 01
10 2 45 55 100 01
10 2 45 45 90 90
10 2 76 45 121 22
Tabela 2.10 - Exemplos de soma em complemento de B-1

2-8
2 . 3 . 4 Números com sinal: representação em complemento de B

Para representar números em complemento de B, utiliza-se a fórmula Bn–a. Note-se que, ao


contrário de complemento de (B–1), esta fórmula é aplicada sobre todo o número, e não
sobre os seus algarismos individuais. Desta maneira elimina-se a dupla representação do
zero, e faixa de representação fica:

Faixa de representação, para B par: [ –(Bn/2) , +(B n/2–1) ]

Faixa de representação, para B ímpar: [ –(Bn–1)/2 , +(B n–1)/2 ]

Note-se que a gama de representação é dividida em dois subconjuntos, um para números


negativos e outro para positivos. Não existe mais a dupla representação do zero. Observe-se
também que as faixas (considerando-se a representação dos números) continuam as mesmas
da representação em complemento de B–1. Como estes números são agora interpretados em
complemento de B, entretanto, os valores das faixas mudam. A faixa positiva permanece a
mesma, mas a faixa negativa sofre um deslocamento de uma unidade, de forma a eliminar o
zero negativo. A tabela a seguir ilustra as faixas para diversas bases.

Base Num.dig. Faixa Faixa em decimal


2 4 1000,1001,..,1111,0000,..,0111 –8,–7,..,–1,0,1,..7
3 3 112,120,121,..,222,000,001,..,111 –13,–12,–11,..,–1,0,1,..,13
4 3 200,201,..,333,000,001,..,132,133 –32,–31,..,–1,0,1,..31
8 3 400,401,..,777,000,001,..,376,377 –256,–255,..,–1,0,1,..,255
9 2 45,46,..,88,00,01,..,43,44 –40,–39,..,–1,0,1,..,39,40
10 2 50,51,..,98,99,00,01,..,48,49 –50,–49,..,–2,–1,0,1,..,48,49
Tabela 2.11 - Exemplos de faixas de representação em complemento de B

Na Tabela 2.11, por exemplo, para base decimal com dois dígitos, com números positivos
tem-se a faixa de 0 até 99; com a representação em complemento de 9 obtém-se a faixa de 50
a 99 e 0 a 49. Esta faixa continua a mesma para complemento de 10; e da mesma maneira a
primeira metade (de 50 a 99) representa números negativos; a segunda metade (de 0 a 49)
representa números positivos. Entretanto, agora 99 representa –1 (e não mais zero, como em
complemento de 9) e 50 representa o número –50 (que não tem representação positiva
equivalente!). Ou seja, todos os números negativos sofreram um acréscimo de –1. As
demais propriedades se mantém: um número iniciando por 9, 8, 7, 6 ou 5 é negativo;
iniciando por 0, 1, 2, 3 ou 4 é positivo. Em binário, para 4 dígitos, tem-se a faixa 1000 a
1111 (–8 a –1) e 0000 a 0111 (0 a 7). Números iniciando por 1 são negativos, e iniciando
por 0 são positivos.
Note-se também que agora as bases ímpares tem faixas simétricas, mas que as bases pares
possuem um número negativo a mais, que não tem equivalente positivo dentro da faixa.

Cálculo do valor do número


Um número em complemento de B, tem o seu valor calculado de acordo com os seguintes
passos:

• determinação do sinal: a regra é a mesma de complemento de (B–1): se a


representação do número, interpretado como inteiro positivo, estiver na metade
superior da faixa, ele é negativo. Se estiver na metade inferior da faixa, é positivo. Ou
seja, sendo o número a=Σ n-1xiBi, se a≥ Bn/2, então este número em complemento de
B é negativo. Se a<Bn/2, então sua interpretação é de um número positivo. Obs.: para
bases pares, a regra pode ser simplificada: basta analisar o dígito mais significativo,
conforme já foi explicado.

2-9
• determinação da magnitude do número. Se o número a for positivo, então sua
magnitude é dada por M(a)=Σ n-1xiBi. Se o número a for negativo, para obter seu valor
deve-se calcular o complemento de B do número:
M(a)=Bn–a = Bn – Σ n-1xiBi

Note-se que Bn-1-a é a representação em complemento de (B–1). Assim, uma maneira rápida
de calcular o complemento de B é realizando o complemento de (B–1) de cada algarismo e a
seguir somar um no número resultante. Ou seja, (Bn–1–a)+1 = Bn–a, que é o número
desejado em complemento de B. A tabela a seguir fornece diversos exemplos.

Base Num.dig. Número Sinal Magnitude Num.decim.


2 4 1110 – 2 –2
2 4 1001 – 7 –7
2 4 1010 – 6 –6
2 4 1000 – 8 –8
2 4 0101 + 5 +5
2 4 0111 + 7 +7
3 3 102 + 11 +11
3 3 111 + 13 +13
3 3 112 – 13 –13
3 3 121 – 11 –11
10 2 98 – 2 –2
10 2 99 – 1 –1
10 3 45 + 45 +45
10 3 54 – 46 –46
10 3 50 – 50 –50
Tabela 2.12 - Exemplos de cálculo do valor de um número em complemento de B

Troca de sinal

Para trocar o sinal de um número a em complemento de B, basta calcular Bn-a. Ou, pelo
raciocínio acima, calcula-se o complemento de (B–1), complementando cada algarismo, e
depois soma-se um. A tabela a seguir ilustra diversos casos de troca de sinal. Note-se que,
para bases pares, a troca de sinal do menor número negativo (de maior magnitude) provoca
estouro de representação, pois este número não tem equivalente positivo. Em bases ímpares
isto não ocorre.

Base Num.dig. Número Núm.negado Magnitude


2 4 1110 0010 2
2 4 1001 0111 7
2 4 1010 0110 6
2 4 0101 1011 5
2 4 1000 1000 (estouro) 8 (–8)
3 3 102 121 11
3 3 111 112 13
3 3 121 102 10
10 2 98 02 2
10 2 99 01 1
10 3 45 55 45
10 3 54 46 46
10 3 50 50 (estouro) 50 (–50)
Tabela 2.13 - Exemplos de troca de sinal para números em complemento de B

2-10
Soma de dois números
O raciocínio é análogo ao utilizado em complemento de (B–1). Deve-se analisar os mesmos
casos da soma em complemento de (B–1) e da soma em sinal magnitude. Na tabela a seguir,
deseja-se calcular d=a+c. Os números a e c estão representados em complemento de B, e
quer-se obter d também em complemento de B.
A tabela é praticamente uma cópia da usada para complemento de (B–1); comparando-se as
duas nota-se que a diferença está na eliminação de todos os termos “–1” (que é justamente a
diferença entre (B–1) e (B). Como no caso de complemento de B-1, tem-se seis casos a
serem analisados. A coluna da direita mostra o resultado obtido se for realizada simplesmente
uma soma dos dois operandos, sem a preocupação de analisar previamente os operandos.
Em todos os casos, usam-se sempre as mesmas tabelas de soma de números inteiros (não se
utilizam tabelas especiais para complemento de B). Note-se que em três casos, marcados
com (*), não se obtém diretamente o número já representado em complemento de B; existe
um termo excedente de Bn. Nos outros três casos, não marcados, o resultado obtido já está
correto.

S(a) S(c) S(d) M(d) d (Soma de a + c)


+ + + M(a)+M(c) a+c
– – – M(a)+M(c) Bn-M(a) + Bn-M(c)
Bn+Bn - (M(a)+M(c))
Bn + Bn - M(d)
Bn + d (*)
+ – se M(a)≥M(c), + M(a)–M(c) M(a) + Bn-M(c)
Bn + M(a)-M(c)
Bn + d (*)
se M(a)<M(c), – M(c)–M(a) M(a) + Bn-M(c)
Bn - (M(c)-M(a))
Bn - M(d)
d
– + se M(a)>M(c), – M(a)–M(c) Bn - M(a) + M(c)
Bn - (M(a)-M(c))
Bn - M(d)
d
se M(a)≤M(c), + M(c)–M(a) Bn - M(a) + M(c)
Bn + (M(c)-M(a))
Bn + d (*)
Tabela 2.14 - Soma em complemento de B

Os casos a serem corrigidos são identificados da mesma maneira que em complemento de


(B–1). Somente os casos que produzirem um “vai-um” são os que necessitam de correção.

Para eliminar o termo B n, basta eliminar este “vai-um”; não existe a necessidade de somar
um ao resultado. Para todos os fins práticos, esta “eliminação” é realizada simplesmente
ignorando-se a existência do “vai-um” em um primeiro momento (a ocorrência ou não do
“vai-um” ainda pode influenciar a análise do resultado da soma, mas não a soma
propriamente dita).

A tabela a seguir ilustra exemplos de diversas somas, tanto em base 2 como em base 10,
considerando que os números estão representados em complemento de B.

2-11
Base Num.dig. a c d=a+c d corrigido
2 4 1110 0001 1111 1111
2 4 1001 0111 10000 0000
2 4 1111 0001 10000 0000
2 4 0110 1111 10101 0101
2 4 0101 1000 1101 1101
2 4 0011 0011 0110 0110
2 4 1111 1111 11110 1110
2 4 0001 1110 1111 1111
10 2 98 37 135 35
10 2 99 00 99 99
10 2 99 01 100 00
10 2 45 55 100 00
10 2 45 45 90 90
10 2 76 45 121 21
Tabela 2.15 - Exemplos de soma em complemento de B

2 . 4 Comparação entre os métodos


Dos quatro métodos estudados nas seções anteriores, dois são amplamente utilizados: o de
representação de números positivos e o de representação em complemento de B. Nestas duas
representações, somas são realizadas através das mesmas tabelas; nenhum procedimento
especial é necessário para somar-se dois números em complemento de dois (exceto a
eventual eliminação do “vai-um”). Os outros dois métodos, de sinal magnitude e de
complemento de (B–1), sofrem da desvantagem de necessitarem de procedimentos mais
complexos para realização da soma, além de apresentarem duas representações para o zero.
Em sistemas atuais, a base é binária e utiliza-se para tratamento de números negativos a
representação em complemento de 2.
A título de comparação, a tabela a seguir mostra a interpretação decimal de números binários
de 4 dígitos nas quatro maneiras analisadas.

Binário Int.positivo sinal mag. compl. de 1 compl. de 2


0000 0 +0 0 0
0001 1 1 1 1
0010 2 2 2 2
0011 3 3 3 3
0100 4 4 4 4
0101 5 5 5 5
0110 6 6 6 6
0111 7 7 7 7
1000 8 –0 –7 –8
1001 9 –1 –6 –7
1010 10 –2 –5 –6
1011 11 –3 –4 –5
1100 12 –4 –3 –4
1101 13 –5 –2 –3
1110 14 –6 –1 –2
1111 15 –7 –0 –1
Tabela 2.16 - Interpretação de números binários

2-12
2 . 5 Subtração
A operação de subtração, seja qual for o método de representação utilizado, pode ser
facilmente realizada transformando-a em uma soma:

d = a – c = a + (–c)
Assim, para realizar subtrações, pode-se simplesmente trocar o sinal do subtraendo e somá-
lo ao minuendo. A troca de sinal e a soma seriam então realizadas de acordo com o sistema
de representação utilizado.

A subtração pode, também ser realizada através de tabelas próprias. Neste caso, no lugar de
“vem-um” (carry in), tem-se o “emprestou-um”(borrow in); e no lugar de “vai um” (carry
out) tem-se o “pede-um” (borrow out).

a c d=a-c
0 0 0
0 1 1 e “pede-um”
1 0 1
1 1 0
Tabela 2.17 - Tabela verdade de um meio-subtrator

a c emprestou um d=a–c pede um


0 0 0 0 0
0 0 1 1 1
0 1 0 1 1
0 1 1 0 1
1 0 0 1 0
1 0 1 0 0
1 1 0 0 0
1 1 1 1 1
Tabela 2.18 - Tabela verdade de um subtrator completo

Apesar destas tabelas serem análogas às utilizadas para a soma, a grande maioria dos
computadores não as utiliza; subtrações são realizadas internamente usando-se o método do
complemento do subtraendo.

2 . 6 Estouro de representação

Os números representados em um computador são limitados. Quanto maior o número de


dígitos disponíveis, maior será a faixa dos números representáveis, mas esta faixa sempre
será finita. Assim, ao realizar-se a soma (ou subtração) de dois números, o resultado pode
cair fora da faixa representável. Nestes casos, diz-se que houve estouro ou transbordamento
da representação (overflow). Para representar corretamente o resultado quando ocorreu
estouro necessitaria-se de um dígito a mais para representar o número.

Por exemplo, para números binários de 4 dígitos, tem-se:


1000 + 0001 = 1001 –8 + 1 = –7 (correto)
1000 + 1111 = 0111 –8 + –1 = 7 (incorreto; deveria ser –9)
0111 + 1111 = 0110 7 + –1 = 6 (correto)
0111 + 0011 = 1010 7 + 3 = –6 (incorreto; deveria ser 10)
Note-se que o estouro não está diretamente relacionado com o “vai-um”. Os exemplos acima
ilustram isto. No primeiro caso, não ocorreu nem estouro nem “vai-um”; no segundo caso

2-13
ocorreram tanto estouro como “vai-um”; no terceiro caso ocorreu “vai-um”, mas não estouro;
e no quarto caso não ocorreu “vai-um”, mas ocorreu estouro.

Existe uma regra simples para determinação de estouro em complemento de 2: ocorre estouro
quando o “vai-um” do dígito mais significativo é diferente do “vem-um” para este mesmo
dígito. Note-se que o dígito mais significativo é o utilizado para indicar o sinal do número.
Esta é a maneira como os computadores internamente calculam se o resultado “estourou” ou
não.

Uma outra maneira, que não necessita da análise dos “vai-um” e “vem-um”, utiliza somente
os dígitos mais significativos dos dois operandos e do resultado, ou seja, analisam-se os
sinais dos operandos e do resultado. Esta análise está resumida na tabela a seguir (seja
d=a+c).

Sinal de a Sinal de c Sinal obtido para d Sinal real de d Estouro


+ + + + Não
+ + – + Sim
– – – – Não
– – + – Sim
+ – +/– +/– Nunca ocorre
– + –/+ –/+ Nunca ocorre
Tabela 2.19 - Análise dos casos de estouro de representação

Pela tabela, pode-se observar que:


• se os dois operandos tiverem sinais diferentes, nunca ocorre estouro (a representação
do resultado sempre estará dentro da faixa).
• somente ocorre estouro quando os dois operandos tiverem sinais iguais, e este sinal é
diferente do sinal obtido para o resultado. Ou seja, quando somando-se dois números
positivos obtém-se um resultado negativo, e quando somando-se dois números
negativos obtém-se um número positivo. Nestas duas situações, o sinal do resultado
está obviamente incorreto, e esta é uma indicação clara que ocorreu estouro de
representação.
Assim, em uma soma, ocorre estouro quando S(a)=S(c) e S(a)≠S(d). Analogamente, em
uma subtração ocorre estouro quando S(a)≠S(c) e S(a)≠S(d).

2 . 7 Exercícios propostos

1. Qual o valor decimal de 011011012? Qual a representação binária de 654?

2. Converter para binário os seguintes números decimais:


(a) 39 (b) 0,4475 (c) 256,75 (d) 129,5625
3. Converter para decimal os seguintes números binários:
(a) 01101 (b) 0,001101 (c) 0111011,1011 (d) 010110011

4. Quantos números diferentes podem ser representados em uma palavra binária de 6 bits?
5. Quantos números diferentes podem ser representados em um conjunto de 4 chaves, cada
uma com três posições diferentes?

6. Escrever os 12 primeiros números no sistema de numeração de base 5.

2-14
7. Escrever os 26 primeiros números no sistema de numeração de base 12. Usar a letra A
para o decimal 10 e a letra B para o decimal 11.

8. Representar o número 12,1 em binário de dez bits, com 5 bits de parte inteira e 5 bits de
parte fracionária.

9. Represente o número –4710 usando representações binárias de 8 bits em (a) sinal e


magnitude, (b) complemento de um e (c) complemento de dois.

10. Converter os seguintes números hexadecimais em decimais:


(a) B6C7 (b) D2763 (c) 9,1A
11. Converter os seguintes números octais em binário:
(a) 56 (b) 32,234 (c) 231,2 (d) 3364

12. Converter os seguintes números hexadecimais em binários:


(a) AB2 (b) 12,A (c) 649 (d) 0,D19

13. Converter os seguintes números binários em hexadecimais:


(a) 010110111 (b) 011110,01011 (c) 01110100010101
14. Considerando um processador que opere com a base quaternária utilizando 5 bits.

14.1 Para números inteiros positivos:


a) Quantos números diferentes podem ser representados? (Fórmula de cálculo e valor
decimal)
b) Quais são os limites inferior e superior desta faixa? (Valor quaternário e decimal
correspondente)
c) Como seria representado o número 126 neste sistema?

14. 2 Supondo que se queira representar os números inteiros com sinal, usando sinal e
magnitude:
a) Quantos números diferentes podem ser representados? (Fórmula de cálculo e valor
decimal)
b) Quais são os limites inferior e superior desta faixa? (Valor quaternário e decimal
correspondente)
c) Como seria representado o número 126 neste sistema?
d) Como seria representado o número -126 neste sistema?
14.3 Agora supondo que se quer representar estes números usando complemento de B-1:
a) Quantos números diferentes podem ser representados? (Fórmula de cálculo e valor
decimal)
b) Quais são os limites inferior e superior desta faixa? (Valor quaternário e decimal
correspondente)
c) Como seria representado o número 126 neste sistema?
d) Como seria representado o número -126 neste sistema?

14.4 Agora supondo que se quer representar estes números usando complemento de B:
a) Quantos números diferentes podem ser representados? (Fórmula de cálculo e valor
decimal)
b) Quais são os limites inferior e superior desta faixa? (Valor quaternário e decimal
correspondente)
c) Como seria representado o número 126 neste sistema?
d) Como seria representado o número -126 neste sistema?

15. Considere os pares de números binários de 6 bits indicados abaixo. Efetue a operação de
soma entre eles supondo, independentemente, que:
1) os números estão representados em sinal e magnitude;
2) os números estão representados em complemento de um;

2-15
3) os números estão representados em complemento de dois.
Para cada caso, interprete o resultado, isto é, determine qual é o seu valor numérico e indique
se este valor é o resultado correto da operação para a forma de representação sugerida.
(a) 010101 e 110110 (b) 010101 e 010110 (c) 110101 e 110110

16. Efetuar as seguintes subtrações em um sistema decimal de 4 dígitos, utilizando uma vez
complemento de 9 e outra vez complemento de 10:
(a) 1024–913 (b) 249–137 (c) 119–239

17. Repetir o exercício 16, agora para um sistema binário de 12 bits, uma vez utilizando
complemento de um e outra vez com complemento de dois.
18. Efetuar as operações indicadas abaixo (em decimal) em um sistema binário de 10 bits,
com notação em complemento de dois. Analise o resultado, indicando a eventual existência
de estouro de representação:
(a) 475 + 128 (b) - 506 + -6 (c) 436 – 475
(d) 506 + 6 (e) 128 – 128 (f) - 475 + 511

19. Converta os números 17 e 15 para binário usando 6 bits e efetue a operação de soma
entre eles (17+15), usando as seguintes representações:
a) sinal magnitude;
b) complemento de um;
c) complemento de dois.
Analise os resultados obtidos quanto à correção (sem calcular o seu valor correspondente em
decimal).

20. Repita o exercício 19 para os números 13 e 8.


21. Repita o exercício 19 para os números -17 e -15 (realizando a soma -17+(-15)).
22. Mostre como somar em complemento de um, para n=6 bits, as seguintes parcelas
decimais:
(a) 27 e –7 (b) 27 e 8 (c) 1 e 5

23. Repetir o exercício 23, com as parcelas codificadas em complemento de dois, também
para 6 bits.
24. Usando a técnica de subtrair através de complemento do subtraendo, mostrar como obter
as seguintes diferenças em binário, 6 bits, complemento de um:
(a) 8 – 7 (b) -16 – 16 (c) 15 – 24
25. Repetir o exercício 25 para complemento de dois em 6 bits.

26. Repetir o exercício 25 para sinal / magnitude.

27. Converta os seguintes números para binário, usando a representação destinada a inteiros
positivos, usando o número necessário de bits e efetue a subtrações indicadas usando a
tabela de subtração:
(a) 32 - 15 (b) 31 - 14 (c) 17 - 9

28. Supondo um sistema decimal, com 4 dígitos, que trabalhe com representação de
negativos em complemento de 9, mostre como realizar as operações:
(a) 0136 + 7654 (b) 9998 + 7777 (c) 0010 – 0108
29. Repetir o exercício 28 para representação em complemento de 10.

2-16
Capítulo
TRÊS
Componentes do computador e modelo de von Neumann

3 . 1 Breve histórico

Uma das mais importantes investidas na área computacional, e que merece registro histórico,
foi a do inglês Charles Babbage. Ele projetou dois computadores: Difference Engine
(denominado a seguir “Dispositivo Diferencial”), iniciado em 1823, e o Analytical Engine
(“Dispositivo Analítico”), concebido em 1834, tendo ambos representado grandes avanços
científicos em sua época, embora nenhuma deles tenha sido concluído. O objetivo do
Dispositivo Diferencial era o cômputo automático de tabelas matemáticas. Sua única operação
era a adição, mas a máquina podia resolver grande número de funções úteis pela técnica de
diferenças finitas. Esta máquina foi projetada para polinômios de grau 6 e números binários
de 20 dígitos, mas não foi concluída devido a problemas de inadequação da tecnologia
mecânica disponível. Outra tentativa de Babbage, foi a construção do Dispositivo Analítico,
que deveria realizar qualquer operação matemática automaticamente. Esta máquina já tinha
módulos de armazenamento (memória) e uma unidade operadora (realizando 4 operações
aritméticas). A entrada e saída de dados era feita através de cartões perfurados. Esta máquina
permitia a alteração da seqüência dos comandos executados, dependendo do resultado de
testes realizados. Novamente por problemas técnicos, a construção desta máquina não
chegou ao final. Na tabela a seguir estão reunidas algumas das principais tentativas de valor
histórico no âmbito computacional.

Data Inventor:máquina Capacidade Inovações técnicas


1642 Pascal adição, subtração transferência automática de vai-um;
representação em complemento
1671 Leibnitz adição, subtração, mecanismo para multiplicação
multipl., divisão e divisão
1827 Babbage: avaliação polinomial operação automática
Difference Engine por diferenças finitas com diversos passos
1834 Babbage: computador de mecanismo automático de controle
Analytical Engine propósitos gerais de sequência (programa)
1941 Zuse: Z3 computador de primeiros computadores de
propósitos gerais propósitos gerais operacionais
1944 Aiken: computador de primeiros computadores de
Harward Mark I propósitos gerais propósitos gerais operacionais
Tabela 3.1 - Principais avanços na computação

Uma das primeiras tentativas para construção de computadores eletrônicos foi feita por volta
de 1930 por John Atanasoff, na Universidade Estadual de Iowa. Era uma máquina
construída com base em válvulas para resolução de equações lineares.
O primeiro computador eletrônico de propósitos gerais foi provavelmente o ENIAC
(Eletronic Numerical Integrator and Calculator), construído entre 1943 e 1946, na
Universidade da Pensilvânia, sob a coordenação de J. Mauchly e J. P. Eckert. Analogamente
à primeira máquina de Babbage, parte da motivação do ENIAC foi a necessidade de construir
tabelas de forma automática, por interesse do sistema militar americano (tabelas balísticas).
Fisicamente, era uma máquina enorme que pesava 30 toneladas e empregava cerca de 18000

3-1
válvulas. Para se ter uma idéia do tempo de execução nesta máquina, eram necessários cerca
de 3 ms para realização de uma multiplicação de 10 dígitos (decimais), o que se constituiu em
grande avanço para a época. Ele trabalhava preponderantemente com valores decimais e não
binários. Na Figura 3.1 é mostrada a estrutura básica do ENIAC.

Com o avanço da pesquisa e o conseqüente desenvolvimento tecnológico, houve grandes


modificações nos computadores. Basicamente, ao longo do tempo, a tecnologia e os estilos
usados na construção de computadores apresentam pontos comuns, que nos permitem
classificá-los em gerações. Na tabela a seguir, são apresentadas as gerações de computadores
de acordo com sua classificação histórica.

Geração Tecnologias Característica de Característica de Exemplo


hardware software
1a Válvulas, aritmética de linguagem de IAS,
(1946 - memória de tubos ponto fixo máquina, linguagem UNIVAC
54) catódicos assembler
2a Transistores, núcleos ponto flutuante ling. alto-nível IBM7094
(1955 - de ferrite, discos registrador índice bibliot. de subrotinas CDC1604
64) magnéticos processadores E/S monitores batch
3 a Circuitos integrados microprogramação multiprogramação IBM
(1965 - (SSI e MSI) pipeline multiprocessamento S/360;
74) memória cache sistema operacional DEC
memória virtual PDP-8
4a Circuitos LSI Amdahl
(1975 - memórias 470;
?) semicondutoras Intel 8748
Tabela 3.2 - Gerações de computadores

Atualmente, esta tabela [HAY78] já poderia ser completada com uma quinta geração que
incluiria as máquinas maciçamente paralelas, os circuitos VLSI, as máquinas “data-flow”,
etc, dependendo do parâmetro escolhido para embasar esta evolução.

leitora de impressora e
cartões perf. de cartões

a
divisor c
tabelas de u
multiplicador e raiz m
funções u
quadrada
l.

unidade mestre de
programação

Figura 3.1 - Estrutura básica do ENIAC

3-2
3 . 2 Princípios básicos
Cada computador tem um conjunto de operações e convenções único para determinar as
posições dos dados com os quais a operação será realizada. Os vários computadores diferem
nas operações específicas que fornecem e nos métodos que eles usam para referenciar dados
que serão manipulados por uma operação. Em geral, uma operação tem a forma

OPERAÇÃO OPERANDOS

e é denominada de instrução. OPERAÇÃO especifica a função que será desempenhada.


OPERANDOS fornece a maneira de calcular a posição atual dos dados com os quais a
operação será realizada.
Um programa é constituído de uma seqüência pré-determinada de instruções, que deve ser
seguida para que seja atingido o objetivo computacional. Este programa e os dados
correspondentes estão armazenados na memória da máquina; o conjunto de instruções (ou
programa) deve ser interpretado para realização do processamento, isto é, a informação
codificada correspondente às ações e operandos deve ser entendida e então processada.
A memória de um sistema de computador tem a função de armazenar dados e instruções; é
organizada em posições, que podem ser visualizadas como elementos em uma matriz. Cada
elemento tem um endereço. Assim, pode-se falar de uma memória que tenha x posições:
cada posição pode ser referenciada diretamente de acordo com a sua colocação na seqüência.
Por exemplo, se uma memória tem 4096 posições, existem posições de memória 0, 1, 2,
3,...., 4094 e 4095. Quando um destes números aparece nos circuitos de controle
conectados à memória, o conteúdo (o valor que está na posição) será trazido da memória para
os circuitos da unidade de processamento ou a informação na unidade de processamento será
armazenada na memória, dependendo do trabalho associado com o endereço.
Instruções em um computador são executadas em uma seqüência determinada por suas
posições de memória. Na maioria dos computadores (que formam a classe das chamadas
máquinas de von Neumann, cujo modelo básico será visto na seção 3.5), instruções e dados
são distribuídos em posições de memória.
O endereço representa uma posição particular na memória e pode ser formado de várias
maneiras. A representação trivial de um endereço está na parte de uma instrução chamada
campo de endereço. Contudo, há várias maneiras de se modificar um campo de endereço por
valores em outras partes do processador (aritmética de endereços). As diversas
possibilidades e vantagens destas técnicas serão mostradas mais adiante na disciplina.

A unidade lógica e aritmética realiza ações indicadas nas instruções, executando operações
numéricas (aritméticas) e não numéricas (lógicas) além da preparação de informações para
desvios do programa. O controle do programa e a unidade lógica e aritmética formam a
unidade central de processamento (UCP), ou simplesmente processador.

Busca - decodificação - execução de instruções


Um elemento no processador, denominado de contador de instruções ou apontador de
instruções, contém a posição da próxima instrução a ser executada. Quando uma seqüência
de execução de instruções tem início, a instrução cujo endereço está no contador de
instruções é trazida da memória para uma área de armazenamento chamada registrador de
instrução. Este processo consiste na busca de instrução.

A instrução é interpretada por circuitos de decodificação que fazem com que sinais
eletrônicos sejam gerados no processador como resultado do valor do campo de operação,
isto é, decodificam a informação correspondente à operação a ser realizada.
Esses sinais, ou seqüência de sinais, resultam na execução da instrução. Execução é a
aplicação da função do operador nos operandos. Quando a execução de uma instrução é

3-3
terminada, o contador de instruções é atualizado para o endereço de memória da próxima
instrução. Esta instrução é então trazida da memória para o registrador de instruções e
executada, repetindo-se assim o ciclo de busca-decodificação-execução.

A seqüência de instruções pode mudar como resultado de uma instrução que direciona um
desvio (também chamada transferência ou salto). Estas instruções contêm o endereço da
próxima instrução a ser executada ao invés do endereço de um operando. Elas causam
mudanças no fluxo do programa como resultados das condições nos dados. O desvio
condicional representado por uma estrutura de programação de alto nível de IF (teste para
uma condição especificada e alteração do fluxo de programa se a condição é atendida) traduz-
se em algum tipo de instrução de desvio.

3 . 3 Elementos funcionais básicos

Um computador é composto por blocos convencionalmente chamados de memória, unidades


operacionais, unidades de controle e dispositivos de entrada e saída (Figura 3.2).

A unidade operacional e a unidade de controle tem funcionalidade específica, conforme será


visto na seqüência. Reunidas, entretanto, recebem no seu conjunto a denominação de
unidade central de processamento (UCP) ou processador. Memórias, unidades operacionais,
unidades de controle e dispositivos de entrada e saída são formados por elementos de menor
complexidade, tais como registradores, contadores, multiplexadores, seletores,
decodificadores, somadores e portas lógicas (AND, OR, INVERSOR).
Registradores são elementos digitais com capacidade de armazenar dados. Têm associados a
si sinais de carga, que determinam quando serão armazenados novos conteúdos nestes
elementos; ao ser acionado este sinal (carga), o registrador copia para si o dado que está em
suas linhas de entrada. Contadores, multiplexadores, seletores, decodificadores, somadores
e portas lógicas são elementos com capacidade de operar sobre dados, alterando-os ou
fornecendo um novo dado como resultado da operação que realizam. Elementos digitais
necessitam ser ativados ou habilitados para realizar uma determinada operação. Os sinais
responsáveis pela ativação ou habilitação de componentes digitais são conhecidos como
sinais de controle.

memória

unidade
controle operacional

entrada/
saída

Figura 3.2 - Elementos básicos de um computador

Dados são transferidos, entre os diversos elementos de um computador, por caminhos


físicos chamados barramentos. Barramentos são caminhos físicos que permitem o
transporte de dados entre os vários elementos da parte operacional, memória e sistema de

3-4
entrada e saída. Um barramento só pode receber dados de uma fonte de cada vez. Do ponto
de vista de arquitetura, um barramento se caracteriza pela sua largura em bits. A largura em
bits do barramento deve corresponder ao comprimento dos elementos (dados, endereço,
controle) que são por ele transportados.

Cada um dos blocos básicos do computador é comentado, em detalhes, a seguir.

3 . 3 . 1 Memória
A memória é formada por elementos armazenadores de informação. Uma memória está
dividida em palavras. Cada palavra ocupa uma posição de memória e é identificada
univocamente por um endereço. O conteúdo armazenado nas palavras da memória tanto
pode representar dados como instruções. Um esquema da estrutura convencional para a
memória de um computador é mostrado na Figura 3.3.

RDM in

read
R
E memória
M write

RDM out

Figura 3.3 - Modelo estrutural da memória

Os registradores mostrados na Figura 3.3 são:

REM : registrador de endereços da memória - contém o endereço do


dado a ser lido ou escrito na memória.
RDM i n : registrador de dados da memória (entrada) - contém o dado
a ser escrito na memória.
RDM out : registrador de dados da memória (saída) - contém o dado
lido da memória.

Os sinais de controle significam:

read: leitura da memória - o conteúdo da posição de memória


endereçada por REM é copiada em RDMout .
write: escrita na memória - a posição de memória endereçada por REM
recebe o conteúdo de RDMin .

Uma memória é caracterizada por vários parâmetros. Os mais importantes são: tamanho,
velocidade e tecnologia. No nível de arquitetura, interessam: tamanho da palavra em bits e
tamanho da memória em palavras. Estes tamanhos geralmente são indicados sob a forma de
potências de dois. O tamanho da palavra determina o comprimento em bits do RDM
(registrador de dados) e o tamanho da memória o comprimento em bits do REM (registrador
de endereços).

3-5
3 . 3 . 2 Unidade operacional
A unidade operacional, também chamada de bloco operacional, executa as transformações
sobre dados especificadas pelas instruções de um computador. Compõe-se basicamente de
uma unidade lógica e aritmética, de registradores de uso geral e específico e dos barramentos
que interligam todos esses elementos.
O número, tamanho e uso dos registradores e a quantidade e tipo de operações que a unidade
lógica e aritmética realiza são alguns dos fatores que determinam o porte de um processador.

Unidade lógica e aritmética ( ULA )


A ULA realiza operações aritméticas e operações lógicas sobre um ou mais operandos.
Exemplos de operações realizadas pela ULA: soma de dois operandos; negação de um
operando; inversão de um operando; AND (“E” lógico) de dois operandos; OR (“OU” lógico)
de dois operandos; deslocamento de um operando para a esquerda ou direita ; rotação de um
operando para a esquerda ou direita
As operações da ULA são, geralmente, muito simples. Funções mais complexas, exigidas
pelas instruções da máquina, são realizadas pela ativação seqüencial das várias operações
básicas disponíveis. Um exemplo é a execução de instruções de multiplicação em alguns
computadores, que compreende a ativação de operações sucessivas de soma e deslocamento
na ULA.
A ULA fornece o resultado da operação e também algumas indicações sobre a operação
realizada. Tais indicações são chamadas códigos de condição. Exemplos de alguns
códigos de condição comumente gerados na ULA são:
• Overflow: (ou estouro de campo) indica que o resultado de uma operação aritmética não
pode ser representado no espaço (tamanho da palavra) disponível.
• Sinal: indica se o resultado da operação é negativo ou positivo.
• Carry: dependendo da operação realizada (soma ou subtração) pode representar o bit de
vai-um (carry-out) ou vem-um (borrow-out). Usado muitas vezes também
em operações de deslocamento para guardar ou fornecer o bit deslocado. A
indicação de carry não deve ser confundida com overflow.
• Zero: indica se o resultado da operação realizada é nulo.
O modelo estrutural da unidade lógica e aritmética é mostrado na Figura 3.4.

operandos

códigos de
controle condição
U L A

resultado

Figura 3.4 - Modelo estrutural da ULA

3-6
Os sinais de controle que devem ser fornecidos para a ULA servem para selecionar a
operação desejada entre as operações básicas disponíveis. Convém salientar que a ULA não
armazena nem o resultado, nem os operandos, nem os códigos de condição gerados.

Uma ULA se caracteriza por:


• comprimento em bits dos operandos
• número e tipo de operações
• códigos de condição gerados

Acumulador
O acumulador é um registrador e tem por função armazenar um operando e/ou um
resultado fornecido pela ULA. Nos computadores mais simples é encontrado apenas um
acumulador. Em algumas arquiteturas mais complexas vários registradores podem
desempenhar as funções de um acumulador.

Como todos os registradores, o acumulador é ativado por um sinal de controle de carga. A


cada sinal de carga, o dado na entrada do registrador é copiado para o seu interior
(obviamente o antigo conteúdo do acumulador é perdido).
Um acumulador, sendo um registrador, se caracteriza ao nível de arquitetura apenas pelo seu
comprimento em bits.

3 . 3 . 3 Unidade de controle
Para gerenciar o fluxo interno de dados e o instante preciso em que ocorrem as transferências
entre uma unidade e outra são necessários sinais de controle. Esses sinais são fornecidos
por um elemento conhecido por unidade de controle.

Cada sinal de controle comanda uma microoperação. Uma microoperação pode ser
responsável pela realização de uma carga em um registrador, uma seleção de um dado para
entrada em um determinado componente, uma ativação da memória, a seleção de uma
operação da ULA ou a habilitação de um circuito lógico, para citar alguns exemplos.

Unidades de controle são máquinas de estado finitas (FSM) realizadas por lógica seqüencial.
Lógica seqüencial e lógica combinacional são caracterizadas, informalmente, como segue:

• Lógica seqüencial: os sinais de saída são função dos sinais de entrada e do estado
anterior do sistema.

• Lógica combinacional: os sinais de saída são função exclusiva dos sinais de


entrada.

Existem várias formas de implementar lógica seqüencial. Tais formas de implementação


caracterizam a organização da unidade de controle. As duas organizações usuais são:
• Organização convencional: a unidade de controle é composta por componentes
digitais como flip-flops, contadores e decodificadores, que geram, seqüencialmente
e nos instantes de tempo adequados, todos os sinais de controle necessários à
ativação da unidade operacional, do sistema de entrada e saída e da memória.

• Organização microprogramada: em uma unidade de controle microprogramada,


os sinais de controle estão armazenados numa memória especial chamada memória
de controle. Vários sinais de controle são buscados a cada acesso à memória de
controle. Esses sinais estão agrupados em longas palavras chamadas
microinstruções. Um conjunto de microinstruções forma um microprograma.

3-7
A unidade de controle, baseada em sinais de entrada obtidos do registrador de estado (RST)
e do registrador de instruções (RI), gera como saída todos os sinais de controle necessários
para a unidade operacional (Figura 3.5).

O registrador de instruções é um elemento do bloco de controle, o registrador de estado é um


elemento da interface entre a unidade de controle e a unidade operacional. Em função de uma
arquitetura específica, esse último registrador tanto pode aparecer classificado numa unidade
como em outra.

RI unidade
de sinais de
controle controle
RST

Figura 3.5 - Esquema de uma unidade de controle

3 . 3 . 4 Registradores especiais
Existem, no computador, alguns registradores com funções especiais, conforme será
explicado a seguir. Dependendo da arquitetura e da organização de cada máquina, alguns
registradores podem estar posicionados na unidade de controle ou na unidade operacional.
Esta localização, entretanto, no momento não é relevante; aqui será assumida a posição
adotada por cada máquina estudada.

Apontador de instruções
O apontador de instruções é um registrador e tem por função manter atualizado o
endereço de memória da próxima instrução que deve ser executada. Também é chamado de
contador do programa (ou PC, do inglês Program Counter). O nome contador do
programa se deve ao fato de, no modelo básico de um computador, instruções consecutivas
de um programa serem armazenadas em palavras da memória que possuem endereços
também consecutivos. Assim, para acessar a próxima instrução, basta contar mais um.
Do ponto de vista de arquitetura, um apontador de instruções se caracteriza pelo seu
comprimento em bits. Como o PC contém um endereço de memória, o comprimento do PC é
função do tamanho da memória onde estão armazenados os programas em execução.

Registrador de instruções ( RI )

O registrador de instruções armazena a instrução que está sendo executada. Em função


do conteúdo desse registrador, a unidade de controle determina quais os sinais de controle
devem ser gerados para executar as operações determinadas pela instrução. Do ponto de vista
de arquitetura, um registrador de instruções se caracteriza pelo seu comprimento em bits. O
comprimento do RI depende do tamanho e codificação das instruções do computador.

Registrador de estado ( RST )

O registrador de estado armazena códigos de condição gerados pela unidade lógica e


aritmética (e, eventualmente, por outros elementos, como os sinais de interrupção gerados
por dispositivos de entrada e saída). Em função do conteúdo desse registrador, a unidade de
controle toma decisões sobre a geração ou não de certos sinais de controle. Do ponto de vista

3-8
de arquitetura, um registrador de estado se caracteriza pelo seu comprimento em bits, que é
uma função do número de códigos de condição implementados na máquina.

3 . 3 . 5 Conjunto de instruções e modos de endereçamento

Uma instrução é um conjunto de bits devidamente codificados que indica ao computador que
seqüência de microoperações ele deve realizar. Instruções são classificadas por semelhança
de propósito e formato. Classificações comuns incluem, entre outras:
• instruções de transferência de dados
• instruções aritméticas e lógicas
• instruções de teste e desvio
O conjunto de todas as instruções que um determinado computador reconhece e pode
executar é chamado de conjunto de instruções. Qualquer seqüência finita de instruções
de um determinado conjunto de instruções compõe um programa.

Muitas da instruções de um computador realizam operações sobre operandos (p.ex. soma


de dois elementos). Operandos podem estar em qualquer posição da memória ou em
qualquer registrador (inclusive em um registrador pertencente a um dispositivo de entrada e
saída). Para que a unidade de controle saiba onde achar um operando é necessário que o
endereço do operando apareça junto a instrução. Nas instruções de desvio (parecidas
com GOTO e IF-THEN em linguagens de alto nível) é necessário indicar, ao invés de
endereço de operando, para qual posição ou endereço de programa se quer desviar. As
diversas formas em que o endereço de um operando pode aparecer, somadas às diversas
formas em que um endereço de desvio pode ser encontrado em um computador, caracterizam
os modos de endereçamento desse computador.

3 . 3 . 6 Ciclo de busca-decodificação-execução de instruções

Busca, decodificação e execução de instruções são as tarefas básicas realizadas por um


processador. Caracterizam um ciclo, pois as tarefas são executadas repetidamente, sempre e
sempre, até que seja decodificada uma instrução que indique parada ao computador (halt,
stop, wait, etc).

Busca: na fase de busca é lida uma instrução da memória. Essa fase envolve:
• copiar o apontador de programa (PC ) para o registrador de endereços da
memória (REM),
• ler uma instrução da memória,
• copiar o registrador de dados da memória (RDM) para o registrador de
instruções (RI),
• atualizar o apontador de programa (PC ).

Decodificação: nessa fase é determinada qual instrução deve ser executada. A


decodificação geralmente é realizada por lógica combinacional.
Execução: na fase de execução, para cada tipo de instrução é realizado, conforme
necessário:
• cálculo do endereço de operandos,
• busca de operandos da memória,
• seleção de operação da ULA,
• carga de registradores,
• escrita de operandos na memória,
• atualização do PC para desvios.

3-9
O controle de todas as operações do ciclo de busca-decodificação-execução de instruções é
de responsabilidade da unidade de controle. O programador, mesmo o de baixo nível (ou
seja, aquele que conhece a linguagem de máquina de seu computador), não precisa se
preocupar com isso, a não ser para calcular os tempos envolvidos na execução de seu
programa.

3 . 3 . 7 Programação de um processador

A única linguagem que um processador entende e tem condições de executar não é, ao


contrário do que muitos pensam, o inglês, mas sim linguagem de máquina. Linguagem
de máquina é uma imagem numérica (binária) que representa a codificação do conjunto de
instruções de um computador. Todos os programas objeto de um computador estão
representados e armazenados em linguagem de máquina.
Representações numéricas são de difícil manipulação até mesmo por programadores
experientes. Programas em linguagem de máquina são difíceis de elaborar, testar e alterar.
Para facilitar as tarefas de programação e depuração, mnemônicos (em inglês) foram
associados aos códigos das instruções, nomes aos operandos e rótulos (labels) às
posições ocupadas pelo programa. A partir de então, não é mais necessário trabalhar com
códigos numéricos, pois toda a programação de baixo nível é feita usando símbolos.
Um programa escrito com auxílio de símbolos, ou seja em linguagem simbólica, precisa ser
traduzido em linguagem de máquina para que possa ser executado. Essa tradução recebe o
nome de montagem e, quando automatizada, o programa que a realiza recebe o nome de
montador.
Um montador realiza praticamente apenas uma tradução “um para um” das instruções da
linguagem simbólica para instruções de máquina (ao contrário de um compilador, que gera
rotinas em linguagem de máquina para cada instrução da linguagem de alto nível e depois
otimiza código e alocação de variáveis). Montadores modernos, para computadores de 16 e
32 bits por exemplo, são bastante poderosos e confortáveis, aceitando comandos específicos
(chamados comumente de pseudo-instruções) e tipos de dados não disponíveis na
linguagem de máquina.

3 . 4 Um computador de primeira geração: o EDVAC


No ENIAC, e nas máquinas que o antecederam, os programas e os dados eram armazenados
em memórias separadas. O armazenamento inicial de programas e sua alteração constituía-se
em uma atividade extremamente cansativa. A idéia de armazenar programas e dados na
mesma unidade de memória - que corresponde ao conceito de programa armazenado - é
atribuída ao matemático húngaro John von Neumann (1903 – 1957), que atuou como
consultor no projeto do ENIAC. Sua proposta foi publicada em 1945 para um computador
novo, o EDVAC (Electronic Discrete Variable Computer). Além de facilitar o processo de
programação, o conceito de programa armazenado torna possível ao programa modificar
suas próprias instruções.

Além de ser um computador de programa armazenado, o EDVAC trazia outras inovações:


memória de capacidade bastante aumentada (principal 1 com 1024 palavras, implementada
através de uma linha de atraso em mercúrio, e secundária magnética de 20k palavras);
representação interna em binário; e circuitos aritméticos binários seriais, devido à entrada de
dados serial.
A estrutura básica de um computador de primeira geração, que pode ser correspondente a
uma máquina semelhante ao EDVAC, é mostrada na Figura 3.6.
1
A memória principal deve ser rápida: armazena pelo menos o programa em execução e os dados
correspondentes. A memória secundária é mais lenta e serve para armazenar grandes volumes de dados e
programas que não estão em execução.

3-10
unidades de
unidade memória
central de secundárias
processamento

unidade teletipo
lógico-
memória aritmética
principal leitora de
unidade de
controle de cartões
programa
impressora e
perfuradora
de cartões

equipamentos de E/S
Figura 3.6 - Arquitetura de um computador de primeira geração

Antes da execução de um programa no EDVAC, todas as instruções e dados eram colocados


na memória principal. Uma instrução aritmética, por exemplo, tinha o seguinte formato:
A1 A2 A3 A4 OP

que significava o seguinte: realize a operação OP com o conteúdo das posições de memória
principal, cujos endereços são A1 e A 2 e coloque o resultado na posição A3. O quarto
endereço, A4, especifica a posição da próxima instrução a ser executada.

Uma instrução condicional tinha o seguinte formato:

A1 A2 A3 A4 C

que significava o seguinte: se o número em A1 não é menor do que o número em A2 então


execute a instrução constante na posição A3, senão, execute a instrução em A4.

Ainda existia um par de instruções de entrada e saída para a transferência de informações


entre as memórias principal e secundária. Nestas instruções, o campo do segundo endereço
era dividido em dois componentes, um modificador de operação m, que indicava a direção da
transferência de dados, e um número n, que representava o endereço do condutor no meio de
armazenamento secundário a ser usado. O formato da instrução era:
A1 m,n A3 A4 W

significando:
1. Se m = 1, transfira para o condutor n a seqüência de palavras armazenadas na memória
principal nas posições A1, A1+1, A 1+2, ....., A 3.
2. Se m = 2, transfira do condutor n a seqüência de palavras para as posições A1, A 1+1,
A1+2, ....., A 3 na memória principal.
Novamente A4 era o endereço da próxima instrução a ser realizada.

Observe na estrutura do EDVAC, mostrada na Figura 3.6, que há dois conjuntos de linhas
originados na unidade central de processamento que conduzem informações entre esta
unidade e os equipamentos de entrada e saída. O conjunto de linhas que parte do bloco

3-11
denominado, na figura, de unidade de controle de programa conduz informações de
controle aos elementos de E/S, determinando quais atividades devem ser realizadas nestes
elementos (por exemplo, indicando à leitora de cartões que ela deve efetuar a leitura do
próximo cartão, ou indicando à impressora que ela deve imprimir o caracter que se encontra
disponível). O outro conjunto de linhas, que está relacionado ao bloco unidade lógico-
aritmética conduz dados que serão ou que foram processados. Assim, este conjunto de
linhas transporta dados provenientes da leitora de cartões, transporta dados para a teletipo e
recebidos da mesma, e assim por diante.

O equipamento de entrada e saída do EDVAC consistia de um dispositivo semelhante a uma


máquina de escrever, que transferia informação diretamente para os condutores magnéticos,
e uma impressora, que revertia este processo. O EDVAC tornou-se operacional em 1951.
Principais inconvenientes do EDVAC

O formato de instrução utilizado no EDVAC, bem como a arquitetura e organização


escolhidas para a sua implementação, resultam em problemas, tais como os explicados a
seguir.

Considere o formato de uma instrução aritmética, por exemplo, de uma soma: tem-se quatro
operandos mais o código de operação. Todos os operandos correspondem a endereços de
memória; dois são endereços das parcelas, um é o endereço para o armazenamento do
resultado final, e outro é o endereço da próxima instrução. Isto implica em um grande
número de interações com a memória, que não podem ser feitas paralelamente (considerando
a estrutura proposta). Como o tempo de acesso para leitura e escrita de informações para a
memória é grande, se comparado ao tempo empregado para processamento interno à UCP,
este constitui-se em um entrave significativo aos índices de desempenho do computador.
Este entrave é referido como “gargalo de von Neumann”.
Como formas de resolver ou diminuir o problema, pode-se pensar em soluções tais como
diminuir o número de operandos, fazendo com que alguns deles sejam implícitos. Por
exemplo:
• se o resultado de uma operação de soma for armazenado na posição onde inicialmente
está um dos operandos, não é necessário especificar o endereço do resultado
(obviamente neste caso o valor inicial do operando será perdido);
• se o resultado de uma operação de soma for armazenado em uma posição previamente
convencionada, como no endereço seguinte ao de um dos operandos, para citar um
exemplo, ou em uma posição especial de memória destinada só para este fim, não é
necessário especificar o endereço do resultado;
• se o endereço da próxima instrução for previamente convencionado, não há
necessidade de incluí-lo na instrução.

Esta especificação explícita de endereços traz outro inconveniente. No caso do EDVAC, a


memória principal tem 1024 palavras: são necessários 10 bits para endereçá-la. Portanto,
uma instrução aritmética necessitará mais de 40 bits para sua representação (4 x 10 de
endereçamento mais x bits para definição da operação a ser executada) exigindo palavras de
memória grandes ou esquemas especiais de organização de informações.
Estes problemas começaram a ser resolvidos pelo próprio von Neumann na máquina
seguinte, que ficou conhecida pela sigla IAS.

3 . 5 Modelo de von Neumann: o computador IAS

Em 1946, Von Neumann e sua equipe iniciaram o projeto de um novo computador de


programa armazenado: o computador IAS, elaborado no Instituto de Estudos Avançados de
Princeton (Princeton Institute for Advanced Studies). Esta máquina usava como memória
principal um tubo de raios catódicos de acesso randômico, o que permitia o acesso a uma

3-12
palavra inteira em uma única operação. Cada instrução continha somente um endereço de
memória e tinha o seguinte formato:

OP A

Esta máquina foi largamente divulgada, influenciando sobremaneira o projeto subsequente de


outras máquinas. Os blocos básicos componentes do IAS são:
• uma unidade de processamento central, para execução de operações aritméticas e
lógicas;

• uma unidade de controle de programa, para determinar o sequenciamento das instruções


a serem executadas e gerar os sinais de controle para as outras unidades. Estes sinais
determinam as ações a serem executadas;

• uma unidade de memória principal, com capacidade de 4096 palavras;


• uma unidade de entrada e saída.

As ligações entre estas unidades são mostradas na Figura 3.7.


Unidade Central de Processamento

unidade lógica e aritmética


AC MQ
equipamento
circuitos de
lógico-aritméticos entrada e
saída
DR

instruções
e dados

IBR PC
memória

principal
IR AR

circuitos de
endereços
: sinais de
controle :
controle

Unidade de controle de programa


Figura 3.7 - Estrutura do IAS

Na unidade central de processamento do IAS existe um novo elemento de armazenamento de


dados, que é o acumulador (AC). Este elemento atua como uma memória rápida que guarda

3-13
de forma imediata o resultado das operações realizadas na unidade lógico-aritmética. O
acumulador é empregado intensamente em instruções que realizam estas operações.

Suponha-se a realização de um programa que executa a soma de dois números. Os dois


números a serem somados estão armazenados nas posições 100 e 101 de memória. O
resultado deve ser guardado na posição 102. Na descrição abaixo, a seta (←) indica a
transferência de informação; assim, A ← B significa que A recebe o conteúdo de B

Instrução Comentários
AC ← M(100) Transfere conteúdo da memória, end. 100, para acumulador
AC ← AC + M(101) Soma conteúdo da posição 101 ao conteúdo do acumulador e
coloca o resultado no acumulador
M(102) ← AC Armazena o conteúdo do acumulador no endereço 102
Figura 3.8 - Exemplo de programa no IAS

Na Figura 3.7 é mostrada a estrutura do computador IAS, cuja arquitetura básica ficou
conhecida como modelo de von Neumann. A terminologia não corresponde exatamente a
utilizada originalmente; os termos estão atualizados para o seu equivalente próximo na
nomenclatura atual.
Organização da memória

A memória do IAS tinha 212 = 4096 palavras, sendo as palavras compostas por 40 bits.
Estes 40 bits, eram a quantidade de informações que podiam ser transferidas, em cada
momento, da UCP para a memória (em um passo). Estas palavras armazenadas na memória
podiam corresponder a instruções ou a dados.
Formato dos dados

Os dados eram números binários representados em ponto fixo e codificados em


complemento de dois. Sendo assim, o bit mais a esquerda do número correspondia ao sinal
do mesmo e as operações de soma e subtração eram ambas executadas por um somador. O
ponto do número estava supostamente entre os bits 0 e 1, permitindo assim a representação
direta apenas de números situados no intervalo entre 0 e 1; os demais números deveriam ser
previamente ajustados antes da realização de cálculos. O formato de um dado é representado
a seguir.
0 1 39


Bit de sinal
Figura 3.9 - Formato dos dados do IAS

Formato das instruções: cada instrução podia ser representada com 20 bits; portanto,
uma palavra de memória podia acomodar 2 instruções. Oito bits, os mais da esquerda ou
“mais significativos” eram usados para o código da operação a ser realizada, e os outros doze
bits eram usados para especificar o endereço de uma posição de memória, conforme
mostrado abaixo.
Instrução posicionada à esquerda Instrução posicionada à direita
0 8 19 20 28 39
cód.operação endereço cód.operação endereço
Figura 3.10 - Formato das instruções do IAS

3-14
Comparando-se esta instrução com o formato de instrução anteriormente apresentado para o
EDVAC, na qual eram expressos 4 endereços, pode-se observar que ocorreu uma redução
substancial no comprimento da palavra de instrução. As alterações na organização do IAS,
que tornaram possível esta redução são as seguintes:

• há registradores pré-definidos na UCP para armazenarem operandos e resultados,


isto é, os endereços dos registradores da UCP estão implícitos no código de
operação. Assim, as instruções do IAS utilizam estes registradores automaticamente.

• as instruções de um programa estão armazenadas na memória principal de maneira


quase inteiramente seqüencial (excetuando os casos de desvio apenas), considerado o
seguimento que deve ser usado na execução; portanto, o endereço da próxima
instrução é o endereço atual mais 1. Isto elimina a necessidade do campo onde
constava o endereço da próxima instrução. As situações em que devem ocorrer
desvios são resolvidas por instruções especiais.

3 . 5 . 1 Organização da UCP
A UCP do IAS contém alguns registradores para armazenamento temporário de instruções,
endereços de memória e dados. O processamento de dados é realizado pelos circuitos lógico-
aritméticos. Os circuitos de controle decodificam instruções, direcionam a informação através
do sistema, e fornecem a temporização dos sinais para todas as ações. Existe um relógio para
a sincronização da operação do sistema. Basicamente, existem as seguintes estruturas, cujas
funções são descritas resumidamente:

• memória principal M: para o armazenamento de programas e dados;


• registrador de dados DR (com 40 bits): recebe dados lidos da posição X da memória
(DR ← M(X)) ou fornece dados que serão gravados na posição X da memória
(M(X) ← DR). Também pode ser usado para armazenar dados temporariamente,
durante a execução de uma instrução;
• registrador de endereços AR (com 12 bits): armazena o endereço que servirá para
apontar posições da memória;

• acumulador AC: armazenamento temporário de operandos;


• registrador multiplicador-quociente MQ: armazenamento temporário de operandos;

• registrador IBR: como no IAS são lidas simultaneamente duas instruções da memória,
a que não é executada imediatamente é armazenada no IBR; a que será executada em
seguida, o código de operação é armazenado no registrador de instruções IR, onde é
decodificado. O campo de endereço desta instrução é transferido para AR;

• contador de programa PC ou registrador de endereço da instrução: armazena o


endereço da próxima instrução a ser executada.

3 . 5 . 2 Conjunto de instruções

As instruções do IAS estão listadas na Tabela 3.3 a seguir. Elas estão divididas em
agrupamentos de acordo com a sua função: instruções de transferência, de desvio
condicional e incondicional, aritméticas e de modificação de endereço.

Para a descrição é usada uma linguagem de transferência a nível de registradores, que


descreve brevemente as instruções e microoperações que ocorrem a nível interno no
computador. As posições de armazenamento são descritas por siglas (como, AR, M), a
transferência de informações é descrita por uma seta, e os elementos de armazenamento

3-15
“matriciais”, como a memória, são especificados por índices entre parênteses. Portanto, na
memória de 4096 x 40, como é o caso do IAS, M(X,0:19) indica os bits de 0 a 19 da palavra
na posição ou endereço X da memória.

Conjunto de instruções do computador IAS


Tipo instrução Notação Descrição
Transferência de AC ← MQ Transfere conteúdo do reg. MQ para o
dados acumulador AC
MQ ← M(X) Transfere conteúdo da posição X de
memória para MQ
M(X) ← AC Transfere conteúdo de AC para a posição
de memória X
AC ← M(X) Transfere M(X) para AC
AC ← – M(X) Transfere – M(X) para AC
AC ← |M(X)| Transfere valor absoluto de M(X) para AC
AC ← – |M(X)| Transfere – |M(X)| para AC
Desvio go to M(X, 0:19) busque a próxima instrução da metade
incondicional esquerda de M(X)
go to M(X, 20:39) busque a próxima instrução da metade
direita de M(X)
Desvio se AC ≥ 0 então desvie se o número em AC não for negativo,
condicional para M(X,0:19) busque a próxima instrução na metade
esquerda de M(X)
se AC ≥ 0 então desvie se o número em AC não for negativo,
para M(X,20:39) busque a próxima instrução na metade
direita de M(X)
Aritmética AC ← AC + M(X) Some M(X) a AC; resultado em AC
AC ← AC + |M(X)| Some |M(X)| a AC; resultado em AC
AC ← AC – M(X) Subtraia M(X) de AC; resultado em AC
AC ← AC – |M(X)| Subtraia |M(X)| de AC; resultado em AC
AC,MQ ← MQ * M(X) Multiplique M(X) por MQ; coloque os bits
mais significativos do resultado em AC e
os menos signif. em MQ
MQ,AC ← AC ÷ M(X) Divida AC por M(X); coloque o quociente
em MQ e o resto em AC
AC ← AC x 2 Multiplique AC por 2; i.e., desloque em
um bit à esquerda
AC ← AC ÷ 2 Divida AC por 2; i.e., desloque em um bit
à direita
Modificação de M(X,8:19) ← AC(28:39) Substitua campo de endereço à esquerda de
endereço M(X) pelos 12 bits mais à direita de AC
M(X,28:39) ← AC(28:39) Substitua campo de endereço à direita de
M(X) pelos 12 bits mais à direita de AC
Tabela 3.3 - Conjunto de instruções do computador IAS

As instruções de transferência possibilitam a transferência entre posições de memória e


registradores da UCP, sem que haja modificação nas informações (exceto nos casos em que
é alterado o sinal). As instruções de desvio fazem com que seja buscado um novo endereço,
para a próxima instrução, na memória (se atendida uma condição, no caso do desvio
condicional). Estas instruções portanto permitem a alteração da seqüência de execução de um
programa.

3-16
As instruções aritméticas fornecem os comandos básicos de processamento de informações
ao computador. As instruções de modificação de endereço permitem o cômputo de endereços
na unidade lógico-aritmética e sua inserção posterior em instruções armazenadas na memória.
Esta propriedade permite que o computador altere suas próprias instruções, o que se constitui
em uma característica importante das máquinas de programa armazenado.

3 . 6 Arquiteturas de 4, 3, 2, 1 e 0 endereços

Enquanto o EDVAC trabalhava basicamente com 4 endereços, o IAS apresentava somente


um endereço. Mesmo assim, ambas arquiteturas possuíam as mesmas capacidades. Para
ilustrar como isto é possível, e também para analisar como a quantidade de endereços de
memória influencia nas instruções e na própria arquitetura, são exemplificadas a seguir
máquinas de 4 endereços (como o EDVAC), de 3 endereços, de 2 endereços, de 1 endereço
(como o IAS) e até as máquinas de zero endereços (por endereço entende-se uma referência à
memória). Ao invés de uma análise detalhada de arquiteturas típicas de cada caso, será
apresentada unicamente a programação de uma equação aritmética:

A = ((B + C)*D + E – F) / (G * H)
As letras A até H denotam posições de memória (endereços), e supõe-se que a arquitetura
analisada possui as quatro operações aritméticas (ADD para soma, SUB para subtração,
MUL para multiplicação e DIV para divisão). O formato exato de cada instrução, assim como
os tamanhos de operandos e instruções são irrelevantes nesta análise.

3 . 6 . 1 Arquitetura de 4 endereços

Em uma arquitetura típica de 4 endereços, as instruções apresentam o formato


OP E1 E2 E3 E4

onde OP representa a operação a ser realizada, E1 e E2 indicam a localização dos dois


operados fontes desta operação, E3 indica a localização do operando destino (onde o
resultado deve ser armazenado) e E4 indica o endereço onde está localizada a próxima
instrução a ser executada. Note-se que a ordem destes 4 endereços dentro da instrução pode
variar.

Um programa que execute o cálculo da equação acima pode ser visto a seguir. Note-se que
este programa foi otimizado para evitar o uso de outras posições adicionais de memória, e
representa somente uma entre inúmeras implementações possíveis.

Endereço Instrução Comentário


e1 ADD B C A e2 Soma B com C, resultado em A; vai para e2
e2 MUL A D A e3 Multiplica A por D, resultado em A; vai para e3
e3 ADD A E A e4 Soma A com E, resultado em A; vai para e4
e4 SUB A F A e5 Subtrai F de A, resultado em A; vai para e5
e5 DIV A G A e6 Divide A por G, resultado em A; vai para e6
e6 DIV A H A e7 Divide A por H, resultado final em A; vai para e7
e7 HALT Fim do programa
Figura 3.11 - Programa em uma máquina de 4 endereços

Como cada instrução traz implicitamente o endereço da próxima instrução, não existe a
necessidade de instruções explícitas de desvio (como JUMP e BRANCH). Além disto, uma
arquitetura de 4 endereços permite potencialmente a execução de uma operação de

3-17
manipulação de dados e uma operação de desvio do fluxo do programa em uma única
instrução.

Entretanto, a prática demonstrou que normalmente os programas eram escritos


sequencialmente, isto é, uma instrução seguindo a anterior. Com isto modificou-se o
mecanismo de sequenciamento das instruções, o que deu origem às arquiteturas de três
endereços.

3 . 6 . 2 Arquitetura de 3 endereços
Em uma arquitetura típica de 3 endereços, as instruções apresentam o formato

OP E1 E2 E3

onde OP representa a operação a ser realizada, E1 e E2 indicam a localização dos dois


operados fontes desta operação e E3 indica a localização do operando destino.

Para localizar a próxima instrução a ser executada, criou-se um registrador específico,


denominado de PC (do inglês Program Counter, veja-se seção 3.3.3). Implicitamente, o PC
aponta para o endereço seguinte ao da instrução sendo executada. Assim, o programa fica:

Endereço Instrução Comentário


e1 ADD B C A Soma B com C, resultado em A; incrementa PC
e1+1 MUL A D A Multiplica A por D, resultado em A; incrementa PC
e1+2 ADD A E A Soma A com E, resultado em A; incrementa PC
e1+3 SUB A F A Subtrai F de A, resultado em A; incrementa PC
e1+4 DIV A G A Divide A por G, resultado em A; incrementa PC
e1+5 DIV A H A Divide A por H, resultado final em A; incrementa PC
e1+6 HALT Fim do programa
Figura 3.12 - Programa em uma máquina de 3 endereços

Com a redução de 4 para 3 endereços, existe uma redução do tamanho da instrução e uma
consequente redução do tamanho da memória necessária para armazenar os programas.
Naturalmente, perdeu-se um grau de liberdade: a determinação do endereço da próxima
instrução. Existe agora a necessidade de instruções explícitas de desvio (como JUMP e
BRANCH), e não se pode mais executar simultaneamente uma operação de manipulação de
dados e uma operação de desvio do fluxo do programa em uma única instrução. A economia
de memória, assim como a obrigatoriedade de desenvolvimento de programas sequenciais
(mais fáceis de serem testados, entendidos e corrigidos), entretanto, compensavam este grau
de liberdade perdido.

Instruções de três endereços, entretanto, ainda consomem muita memória. Além disto,
observando-se o programa acima, nota-se que na maioria da vezes um dos operandos fonte e
o operando destino indicam o mesmo endereço (como é o caso do operando A). Com isto
modificou-se a maneira de indicar os operandos, o que deu origem às arquiteturas de dois
endereços.

3 . 6 . 3 Arquitetura de 2 endereços
Em uma arquitetura típica de 2 endereços, as instruções apresentam o formato
OP E1 E2
onde OP representa a operação a ser realizada, E1 e E2 indicam a localização dos dois
operados fontes desta operação e E1 também indica a localização do operando destino.
Assim, E1 tem dupla função, e não são mais possíveis instruções com três operandos

3-18
distintos. Isto permite uma maior redução nos bits necessários para especificar os endereços
dos operandos, mas introduz uma restrição séria: o resultado da operação será armazenado
no endereço de um dos dois operandos fonte, ou seja, um destes dois operandos será
necessariamente alterado. Esta restrição foi contornada com a criação de uma classe extra de
instruções, as instruções de movimentação de dados, que permitem copiar operandos de uma
posição para outra, conforme pode ser visto no programa a seguir:

Endereço Instrução Comentário


e1 MOV A B Move B para A
e1+1 ADD A C Soma A com C, resultado em A
e1+2 MUL A D Multiplica A por D, resultado em A
e1+3 ADD A E Soma A com E, resultado em A
e1+4 SUB A F Subtrai F de A, resultado em A
e1+5 DIV A G Divide A por G, resultado em A
e1+6 DIV A H Divide A por H, resultado final em A
e1+7 HALT Fim do programa
Figura 3.13 - Programa em uma máquina de 2 endereços

Comparando-se este programa com o da arquitetura de três endereços, nota-se que a primeira
instrução (ADD B C A) foi substituída por outras duas, uma de movimentação (MOV A B) e
outra de manipulação de dados (ADD A C). O resultado final é o mesmo, mas o programa
possui mais instruções. Entretanto, as instruções são menores, o que em termos líquidos
proporciona uma grande economia de bits.

Com a criação de registradores especiais, pode-se reduzir ainda mais o número de endereços,
criando-se então as arquiteturas de um endereço.

3 . 6 . 4 Arquitetura de um endereço
Em uma arquitetura típica de um endereço, as instruções apresentam o formato
OP E1

onde OP representa a operação a ser realizada e E1 indica a localização de um operando de


memória (normalmente um dos operandos fontes da operação). O outro operando é
implicitamente assumido como sendo um registrador específico, o acumulador (normalmente
abreviado por AC, veja-se seção 7.2). O Acumulador assume os papéis de um dos
operandos fonte e do operando destino. Em relação às arquiteturas de dois endereços, agora
as instruções de movimentação de dados devem ser subdivididas de acordo com o sentido da
transferência: se da memória para o acumulador (LDA, de LoaD Acumulator), ou se do
acumulador para a memória (STA, de STore Acumulator).O programa exemplo deve ser
conveniente modificado para refletir esta situação (observe-se a instrução extra no fim do
programa):

Endereço Instrução Comentário


e1 LDA B Move B para Acumulador
e1+1 ADD C Soma Acum. com C, resultado no Acumulador
e1+2 MUL D Multiplica Acum. por D, resultado no Acumulador
e1+3 ADD E Soma Acum. com E, resultado no Acumulador
e1+4 SUB F Subtrai F do Acum., resultado no Acumulador
e1+5 DIV G Divide Acum. por G, resultado no Acumulador
e1+6 DIV H Divide Acum. por H, resultado no Acumulador
e1+7 STA A Armazena Acum. no endereço de A
e1+8 HALT Fim do programa
Figura 3.14 - Programa em uma máquina de um endereço

3-19
Comparando-se este programa com o da arquitetura de três endereços, nota-se que existem
duas instruções adicionais de transferência de dados, uma no início do programa (LDA B) e
outra no fim (STA A). A grande vantagem deste tipo de arquitetura está na economia dos
acessos à memória: praticamente cada operando foi lido ou escrito uma única vez, o que não
ocorre nas arquiteturas anteriores. Este é justamente o papel dos registradores locais (ou
acumuladores): permitir que resultados intermediários ou dados muito utilizados não
precisem ser lidos ou escritos da memória a cada vez que forem utilizados.

3 . 6 . 5 Arquitetura de zero endereços


Em uma arquitetura típica de zero endereços, as instruções apresentam o formato

OP
ou seja, não existe nenhuma referência explícita a endereços de memória onde estejam
localizados os operandos. Uma possível solução para determinar a posição dos operandos é
colocá-los em uma região específica de memória, com determinado mecanismo de acesso.
Uma estrutura muito utilizada para estes fins é uma pilha: os operandos são sempre retirados
do topo da pilha, e o resultado da operação sempre é colocado no topo da pilha. Para facilitar
a operação desta estrutura de pilha, as equações são escritas utilizando-se notação polonesa
reversa, onde o símbolo da operação é escrito após os dois operandos. Assim, por exemplo,
A+B seria escrito AB+. Com isto, a equação exemplo fica:
HGFEDCB+*+–//

E o programa que a implementa seria:

Endereço Instrução Comentário


e1 PUSH H Coloca H no topo (atual) da pilha
e1+1 PUSH G Coloca G no topo da pilha
e1+2 PUSH F Coloca F no topo da pilha
e1+3 PUSH E Coloca E no topo da pilha
e1+4 PUSH D Coloca D no topo da pilha
e1+5 PUSH C Coloca C no topo da pilha
e1+6 PUSH B Coloca B no topo da pilha
e1+7 ADD Topo da pilha recebe B+C
(B e C são retirados da pilha)
e1+8 MUL Topo recebe (B+C)*D
e1+9 ADD Topo recebe (B+C)*D + E
e1+10 SUB Topo recebe (B+C)*D + E - F
e1+11 DIV Topo recebe ((B+C)*D + E - F)/G
e1+12 DIV Topo recebe ((B+C)*D + E - F)/G*H
e1+13 POP A Topo da pilha é armazenado em A
e1+14 HALT Fim do programa
Figura 3.15 - Programa em uma máquina de zero endereços

Note-se que duas instruções (PUSH e POP) na realidade utilizam endereços. Estas são,
entretanto, as duas únicas instruções que necessitam referenciar endereços, e são somente
operações de transferência de dados. Uma arquitetura pura de zero endereços não apresenta
vantagens marcantes sobre arquiteturas de um ou dois endereços, e por causa disto não são
muito difundidas. Os computadores atuais, entretanto, possuem estruturas do tipo pilha para
propósitos específicos. Isto será assunto de outras disciplinas.

3-20
Capítulo
QUATRO
Computador hipotético Neander

O computador NEANDER foi criado com intenções didáticas1 e é extremamente simples.


Tão simples que você, sem muito esforço, pode projetá-lo. Experimente!

4 . 1 Características

O computador NEANDER tem as seguintes características:


• Largura de dados e endereços de 8 bits
• Dados representados em complemento de dois
• 1 acumulador de 8 bits (AC)
• 1 apontador de programa de 8 bits (PC)
• 1 registrador de estado com 2 códigos de condição: negativo (N) e zero (Z)

4 . 2 Modos de endereçamento
O NEANDER só possui um modo de endereçamento: o modo direto (muitas vezes também
chamado de absoluto).
No modo de endereçamento direto (Figura 4.1), a palavra que segue o código da instrução
contém, nas instruções de manipulação de dados, o endereço de memória do operando.

Nas instruções de desvio, o endereço contido na instrução corresponde à posição de


memória onde está uma instrução a ser executada.

memória

endereç

operando

Figura 4.1- Modo de endereçamento direto

1
Esta pseudo-máquina foi criada pelos Profs. Raul Weber e Taisy Weber para a antiga disciplina CPD148 -
Arquitetura de Computadores I. Possui simulador e depurador associados, que podem ser vistos no apêndice A.

4-1
4 . 3 Conjunto de instruções
O conjunto de instruções de NEANDER compreende 11 instruções, codificadas através dos
quatro bits mais significativos da palavra que contém o código da instrução (Tabela 4.1):

Código Instrução Comentário


0000 NOP nenhuma operação
0001 STA end armazena acumulador - (store)
0010 LDA end carrega acumulador - (load)
0011 ADD end soma
0100 OR end “ou” lógico
0101 AND end “e” lógico
0110 NOT inverte (complementa) acumulador
1000 JMP end desvio incondicional - (jump)
1001 JN end desvio condicional - (jump on negative)
1010 JZ end desvio condicional - (jump on zero)
1111 HLT término de execução - (halt)
Tabela 4.1 - Conjunto de instruções do NEANDER

Na Tabela 4.1, end significa endereço direto. Nas instruções STA, LDA, ADD, OR e AND,
end corresponde ao endereço de operando. Nas instruções JMP , JN e JZ, end corresponde
ao endereço de desvio. As ações efetuadas por cada uma das instruções da Tabela 4.1 podem
ser vistas na Tabela 4.2, a seguir:

Instrução Comentário
NOP nenhuma operação
STA end MEM(end) ← AC
LDA end AC← MEM(end)
ADD end AC← MEM(end) + AC
OR end AC← MEM(end) OR AC
AND end AC← MEM(end) AND AC
NOT AC← NOT AC
JMP end PC← end
JN end IF N =1 THEN PC ← end
JZ end IF Z =1 THEN PC ← end
Tabela 4.2 - Ações executadas

Na Tabela 4.2 AC é o acumulador, MEM (end) significa conteúdo da posição end de


memória, N e Z são os códigos de condição e ← representa uma atribuição.

4 . 4 Códigos de condição
A unidade lógica e aritmética de NEANDER fornece os seguintes códigos de condição, que
são usados pelas instruções JN e JZ (vide Tabela 4.2):

N - (negativo) : sinal do resultado


1 - resultado é negativo
0 - resultado é positivo

Z - (zero) : indica resultado igual a zero


1 - resultado é igual a zero
0 - resultado é diferente de zero

4-2
As instruções lógicas e aritméticas (ADD, NOT, AND, OR) e a instrução de transferência LDA
afetam os códigos de condição N e Z. As demais instruções (STA, JMP, JN, JZ, NOP e HLT)
não alteram os códigos de condição.

4 . 5 Formato das instruções

As instruções de NEANDER são formadas por um ou dois bytes, ou seja, ocupam uma ou
duas posições na memória (Figura 4.2).

7 4 3 0
código don't care

endereço direto

Figura 4.2 - Formato de instrução no NEANDER

Nas instruções de um byte, os 4 bits mais significativos contém o código da instrução. Nas
instruções de dois bytes, o primeiro byte contém o código (também nos 4 bits mais
significativos) e o segundo byte contém um endereço. Instruções de dois bytes, no
NEANDER, são aquelas instruções que fazem referência à memória.

4 . 6 Exemplo de programação
Vamos considerar, como exemplo, um programa que realiza a soma de 3 posições
consecutivas da memória e armazena o resultado numa quarta posição. Inicialmente, devem
ser escolhidas a área de dados e a área de programa, ou seja, a localização das instruções e
dados na memória. Não existem critérios para essa escolha, mas deve ser observado que a
área de programa não pode invadir a área de dados e vice-versa. Seja, para esse programa,
escolhida uma alocação de memória de tal forma que o programa ocupe a metade inferior da
memória e os dados a metade superior, como segue:
área de programa
início do programa posição 0 (0H)

área de dados
primeira parcela posição 128 (80H)
segunda parcela posição 129 (81H)
terceira parcela posição 130 (82H)
resultado posição 131 (83H)

O programa seria:
Simbólico Comentários
LDA 128 % acumulador A recebe conteúdo da posição 128
ADD 129 % conteúdo de A é somado ao conteúdo da posição 129
ADD 130 % conteúdo de A é somado ao conteúdo da posição 130
STA 31 % conteúdo de A é copiado na posição 131
HLT % processador para

4-3
Esse programa pode ser editado em linguagem de máquina (tanto em hexa como em
decimal), depurado e executado usando o simulador/depurador NEANDER, cujos comandos
foram apresentados no capítulo respectivo. A codificação em linguagem de máquina
correspondente a cada uma das instruções mostradas acima seria:

Simbólico Hexa Decimal


LDA 128 20 80 32 128
ADD 129 30 81 48 129
ADD 130 30 82 48 130
STA 131 10 83 16 131
HLT F0 240

4 . 7 Conclusão

NEANDER é um computador muito simples, desenvolvido apenas para fins didáticos.


Processadores modernos são muito mais complexos que NEANDER. Entretanto, mesmo
processadores utilizados nas mais sofisticadas estações de trabalho são baseados nos
conceitos elementares que você aprendeu com NEANDER.

4 . 8 Exercícios de programação usando o NEANDER

Os exercícios apresentados aqui são apenas uma amostra do que pode ser programado com
NEANDER. Na definição de novos problemas, o único cuidado que deve ser tomado é com
a memória disponível para programa e dados, que compreende apenas 256 posições. Exceto
onde explicitado, todos os números e endereços são representados na base decimal.
Para todos os programas sugeridos, vale a seguinte convenção:
início do programa - posição 0 (0H)
início da área de dados - posição 128 (80H)
Essa convenção é adotada apenas para facilitar a correção dos programas.
1. Limpar o acumulador: faça 4 programas diferentes que zerem o acumulador.
2. Somar duas variáveis de 8 bits: faça um programa para somar duas variáveis
representadas em complemento de dois. As variáveis e o resultado estão dispostos
segundo o mapa de memória abaixo:
posição 128: primeira variável
posição 129: segunda variável
posição 130: resultado

3. Subtrair duas variáveis: faça um programa para subtrair duas variáveis de 8 bits
representadas em complemento de dois. O resultado deve aparecer na posição de memória
consecutiva às ocupadas pelas variáveis.
posição 128: minuendo
posição 129: subtraendo
posição 130: resultado

4. Comparação: determine qual a maior de 3 variáveis positivas de 8 bits armazenadas em


posições consecutivas de memória. O resultado (ou seja, a maior variável), deve aparecer
na primeira posição livre de memória na área reservada aos dados.
5. Determinação de overflow na soma: faça um programa que determine a ocorrência de
overflow na soma de duas variáveis. As variáveis são de 8 bits em complemento de dois e
estão armazenadas em posições consecutivas de memória. O resultado da soma, também

4-4
em 8 bits, deve aparecer na primeira posição livre e overflow deve ser indicado da
seguinte forma:

posição 130: conteúdo = 0H quando não ocorreu overflow


conteúdo = FFH quando ocorreu overflow

6. Limpeza de uma área de memória: faça um programa para zerar 32 posições consecutivas
na memória.

4-5
Capítulo
CINCO
Computador hipotético Ahmes

O computador AHMES1 foi criado com intenções didáticas, para possibilitar a


implementação de rotinas aritméticas simples (como adição e subtração) e a programação de
rotinas relativamente complexas (como multiplicação e divisão). É compatível com o
NEANDER, mas apresenta instruções extras para facilitar a execução de operações
aritméticas.

5 . 1 Características
O computador AHMES tem as seguintes características:
• Largura de dados e endereços de 8 bits.
• Dados representados em complemento de dois.
• 1 acumulador de 8 bits (AC), onde é armazenado o resultado das operações.
• 1 apontador de programa de 8 bits (PC), que indica qual a próxima instrução a ser
executada.
• 1 registrador de estado com 5 códigos de condição: negativo (N), zero (Z), carry
out (vai-um) (C), borrow out (empresta-um) (B) e overflow (estouro) (V).

5 . 2 Modos de endereçamento
O AHMES só possui um modo de endereçamento: o modo direto. Neste modo, a palavra
que segue o código da instrução contém, nas instruções de manipulação de dados, o
endereço do operando (Figura 5.1). Nas instruções de desvio, o endereço contido na
instrução corresponde ao endereço da próxima instrução.

memória
instrução

endereço

operando

Figura 5.1 - Modo de endereçamento direto

1
Este computador simulado foi batizado em homenagem ao escriba Ahmes, do antigo Egito (1650 A.C.),
autor de uma série de papiros contendo regras que possibilitavem cálculos aritméticos “complexos”, como o
cálculo de área de polígonos e manipulação de frações.

5-1
5 . 3 Conjunto de instruções
O conjunto de instruções de AHMES compreende 24 instruções, codificadas através de um
byte de código (Tabela 5.1). Note-se que, na maioria das vezes, os quatro bits mais
significativos são suficientes para definir completamente a instrução.

Código binário Código binário Código Código Instrução


(relevante) (com zeros) hexadecimal decimal (mnemônico)
0000 xxxx 0000 0000 00 0 NOP
0001 xxxx 0001 0000 10 16 STA end
0010 xxxx 0010 0000 20 32 LDA end
0011 xxxx 0011 0000 30 48 ADD end
0100 xxxx 0100 0000 40 64 OR end
0101 xxxx 0101 0000 50 80 AND end
0110 xxxx 0110 0000 60 96 NOT
0111 xxxx 0111 0000 70 112 SUB end
1000 xxxx 1000 0000 80 128 JMP end
1001 00xx 1001 0000 90 144 JN end
1001 01xx 1001 0100 94 148 JP end
1001 10xx 1001 1000 98 152 JV end
1001 11xx 1001 1100 9C 156 JNV end
1010 00xx 1010 0000 A0 160 JZ end
1010 01xx 1010 0100 A4 164 JNZ end
1011 00xx 1011 0000 B0 176 JC end
1011 01xx 1011 0100 B4 180 JNC end
1011 10xx 1011 1000 B8 184 JB end
1011 11xx 1011 1100 BC 188 JNB end
1110 xx00 1110 0000 E0 224 SHR
1110 xx01 1110 0001 E1 225 SHL
1110 xx10 1110 0010 E2 226 ROR
1110 xx11 1110 0011 E3 227 ROL
1111 xxxx 1111 0000 F0 240 HLT
Tabela 5.1 - Conjunto de instruções do AHMES

A primeira coluna da Tabela 5.1 indica, em binário, quais são os bits relevantes da
codificação. Somente os bits indicados em zero e em um são relevantes para identificar a
instrução. Os bits marcados com um “x” são irrelevantes (don’t care), ou seja, seu valor não
interfere na decodificação da instrução. Por simplicidade, todos os “x” serão substituídos por
zeros, como pode ser visto nas demais colunas.

A última coluna indica o mnemônico da instrução, ou seja, uma sigla de duas ou três letras
que visa facilitar a “compreensão” da instrução por um ser humano. Para o computador estes
mnemônicos são desnecessários, uma vez que ele sempre trabalha com códigos binários.
Note-se inclusive que os próprios códigos hexadecimal e decimal também só são usados para
a conveniência humana.

A Tabela 5.2 mostra a execução de cada instrução, tal como ela é realizada pelo computador.
Nesta tabela, AC representa o acumulador, PC representa o program counter (apontador
de instruções), end indica um endereço de memória, MEM(end) o conteúdo da posição de
memória endereçada por end, e N, V, Z, C e B indicam os códigos de condição negativo,
overflow, zero, carry e borrow, respectivamente.

As instruções podem ser divididas em diversas classes ou categorias, de acordo com a sua
função principal. As instruções STA e LDA formam o grupo de movimentação de dados, ou
seja, são responsáveis por levar os dados de e para a memória. As instruções ADD e SUB são
as instruções aritméticas, e as instruções OR, AND e NOT formam o grupo das instruções

5-2
lógicas, uma vez que usam operações da álgebra booleana (mas manipulando oito bits de
cada vez, e não um só bit). As instruções JMP , JN, JP, JV, JNV, JZ, JNZ, JC, JNC, JB e JNB são
as instruções de desvio, e nelas end corresponde ao endereço de desvio, ou seja, qual o
endereço da próxima instrução a ser executada. As instruções SHR , SHL, ROR e ROL são as
instruções de deslocamento.

Instrução Execução Comentário


NOP nenhuma operação nenhuma operação
STA end MEM(end) ← AC armazena acumulador na memória (store)
LDA end AC← MEM(end) carrega acumulador da memória (load)
ADD end AC← AC + MEM(end) soma
OR end AC← AC or MEM (end) “ou” lógico
AND end AC← AC and MEM(end) “e” lógico
NOT AC← NOT AC inverte (complementa) acumulador
SUB end AC← AC – MEM(end) subtração
JMP end PC← end desvio incondicional (jump)
JN end IF N =1 THEN PC ← end desvio condicional (jump if negative)
JP end IF N =0 THEN PC ← end desvio condicional (jump if positive)
JV end IF V =1 THEN PC ← end desvio condicional (jump if overflow)
JNV end IF V =0 THEN PC ← end desvio condicional (jump if not overflow)
JZ end IF Z =1 THEN PC ← end desvio condicional (jump if zero)
JNZ end IF Z =0 THEN PC ← end desvio condicional (jump if non-zero)
JC end IF C =1 THEN PC ← end desvio condicional (jump if carry)
JNC end IF C =0 THEN PC ← end desvio condicional (jump if not carry)
JB end IF B=1 THEN PC ← end desvio condicional (jump if borrow)
JNB end IF B=0 THEN PC ← end desvio condicional (jump if not borrow)
SHR C←AC(0); AC(i-1)←AC(i); AC(7)← 0 deslocamento para direita (shift right)
SHL C←AC(7); AC(i)←AC(i-1); AC(0)←0 deslocamento para esquerda (shift left)
ROR C←AC(0); AC(i-1)←AC(i); AC(7)←C rotação para direita (rotate right)
ROL C←AC(7); AC(i)←AC(i-1); AC(0)←C rotação para esquerda (rotate left)
HLT Interrompe o processamento término de execução - (halt)
Tabela 5.2 - Ações executadas

5 . 4 Códigos de condição

A unidade lógica e aritmética de AHMES fornece os seguintes códigos de condição, que são
usados pelas instruções de desvio condicional (conforme Tabela 5.2):
N - (negativo) : sinal do resultado, interpretado como complemento de dois
1 - resultado é negativo
0 - resultado é positivo
Z - (zero) : indica resultado igual a zero, interpretado como complemento de dois
1 - resultado é igual a zero
0 - resultado é diferente de zero

V - (overflow) : indica estouro de representação após uma instrução de soma ou


subtração, interpretando os operandos e o resultado como complemento
de dois
1 - houve estouro de representação do resultado
0 - não houve estouro (resultado está correto)
C - (carry) : indica a existência de um ”vai-um” após uma operação de soma
1 - ocorreu ”vai-um”

5-3
0 - não ocorreu ”vai-um”
B - (borrow) : indica a existência de um ”empresta-um” após uma operação de subtração
1 - ocorreu ”empresta-um”
0 - não ocorreu ”empresta-um”

As instruções do AHMES afetam os códigos de condição conforme indicado na Tabela 5.3.


Note-se que somente as instruções aritméticas, lógicas e de deslocamento (além da instrução
LDA) afetam os códigos de condição. As instruções de desvio (e a instrução STA), apesar de
testarem este códigos, não os alteram.

Instrução Códigos alterados


NOP nenhum
STA end nenhum
LDA end N, Z
ADD end N, Z, V, C
OR end N, Z
AND end N, Z
NOT N, Z
SUB end N, Z, V, B
JMP end desvio incondicional - nenhum
Jxxx end desvios condicionais - nenhum
SHR N, Z, C
SHL N, Z, C
ROR N, Z, C
ROL N, Z, C
HLT nenhum
Tabela 5.3 - Códigos de condição ajustados

Observação: como o AHMES é um computador com propósitos didáticos, os seus códigos


de condição não correspondem exatamente àqueles encontrados em um computador real.
Assim, por exemplo, borrow (B) e carry (C) são normalmente reunidos em um único código
de condição, chamado simplesmente de carry.

5 . 5 Manipulação aritmética
Nos trechos de programas apresentados a seguir e nos capítulos seguintes será utilizada a
notação simbólica, com mnemônicos no lugar de códigos decimais ou hexadecimais. No
simulador AHMES, entretanto, a codificação deverá necessariamente ser realizada em forma
numérica. Esta restrição também possui caracter didático, para que a ”linguagem de
máquina” do computador seja bem exercitada.

5 . 5 . 1 Aritmética em complemento de dois

AHMES trabalha naturalmente com complemento de dois (os seus componentes de hardware
foram projetados para isto). Assim, somas e subtrações são realizadas diretamente através
das instruções de ADD e SUB. Os cinco códigos de condição (N, Z, V, C e B) também
refletem diretamente o resultado destas instruções.
Para inverter o sinal de número, existem duas possibilidades. Seja “a” o número a ter seu
sinal trocado. Então tem-se:

1. Realizar a operação 0 – a, através da operação SUB. Isto exige carregar zero no


acumulador e depois subtrair o número “a”. O resultado está no acumulador.

5-4
2. Realizar a operação not(a) + 1. Isto utiliza a troca de sinal em complemento de um (que
inverte todos os bits do número, e é implementado através da operação NOT) e a
seguir soma um para obter o complemento de dois:
not(a) = –a – 1 (em complemento de um)
not(a) + 1 = –a –1 + 1 = –a (em complemento de dois)

Sobre os códigos de condição, algumas observações importantes devem ser feitas:


1. Carry (C) e overflow (V) não são sinônimos. Conforme foi visto na seção 2.6, em
aritmética de complemento de dois podem ocorrer as quatro combinações possíveis:
sem carry nem overflow, somente carry, somente overflow e tanto carry como
overflow. Isto pode ser verificado com um programa simples, como ilustrado a seguir.
LDA 128 % primeiro operando está na posição 128
ADD 129 % segundo operando está na posição 129
HLT % resultado está no acumulador
Experimente agora com diversos pares de operandos nos endereços 128 e 129:

1.1: 7 e 5, 15 e 12, 100 e 26, 110 e 17. Em todos estes casos, a soma não produz nem
carry nem overflow.
1.2: 7 e 251 (-5 em complemento de dois), 15 e 244 (-12 em complemento), 100 e 230
(-26), 110 e 239 (-17). Nestes casos, a soma produz carry (C=1), mas não overflow
(V=0). Isto indica que o resultado está correto. O mesmo ocorre para 249 e 251 (-7 e -
5 em complemento de dois), 241 e 244 (-15 e -12 em complemento de dois), 156 e
230 (-100 e -26), 146 e 239 (-110 e -17).
1.3: 127 e 5, 116 e 12, 100 e 28, 110 e 120. Nestes casos, não é produzido carry
(C=0), mas ocorre overflow (V=1). Em todos os casos exemplificados, os operandos
são positivos, mas o resultado é negativo, o que indica estouro de representação.
1.4: 128 e 251 (-128 e -5 em complemento de dois), 241 e 136 (-15 e -120 em
complemento de dois), 156 e 226 (-100 e -30), 146 e 238 (-110 e -18). Nestes casos,
ocorre tanto carry (C=1) como overflow (V=1). Em todos os casos exemplificados, os
operandos são negativos, mas o resultado é positivo, o que indica estouro de
representação.
2. O sinal de carry, em múltiplas somas, é cumulativo. Isto significa que, quando se
somam três ou mais parcelas, o número de vai-uns deve ser contado, para determinar
qual a quantidade final (ou seja, se ocorreu um ”vai-dois”, ”vai-três”, etc)

3. O sinal de overflow, em múltiplas somas, deve ser analisado cuidadosamente. Se o


overflow ocorrer um número ímpar de vezes, então é garantido que ocorreu overflow
no resultado final. Se entretanto ocorrer overflow um número par de vezes, isto não
significa necessariamente que ocorreu estouro da representação do resultado final.

Seja, por exemplo, 127 + 1 + -1. Em complemento de dois, tem-se 01111111 +


00000001 + 11111111. A soma das duas primeiras parcelas resulta em 10000000,
com indicação de overflow. A soma seguinte, 1000000 + 11111111, resulta em
011111111, também com indicação de overflow. Neste caso, o resultado final está
correto (127 em decimal), e os dois sinais de overflow anulam-se mutuamente. No
caso de 127 + 127 + 127 + 127, entretanto, também ocorre overflow duas vezes, mas
o resultado final (252 em decimal, ou seja, -4) está incorreto, ou seja, realmente
ocorreu overflow.

4. O sinal de borrow é o inverso do carry. Isto pode ser verificado comparando-se uma
operação de subtração com uma adição com o complemento do subtraendo, ou seja, no

5-5
lugar de a – b realiza-se a + not (b) + 1. Como pode ser visto na tabela abaixo, o carry
resultante é sempre o inverso do borrow:

a+ not(b) + 1 r carry a– b r borrow


0 1 1 0 1 0 0 0 0
0 0 1 1 0 0 1 1 1
1 1 1 1 1 1 0 1 0
1 0 1 0 1 1 1 0 0

Assim, se o carry for um, significa que o borrow é zero, e vice-versa. Isto pode ser
observado comparando-se o borrow (B) da operação 5 – 7 através de uma instrução
de SUB com o carry (C) gerado pela operação 5 + (-7) através de uma instrução de
ADD. Note-se que o AHMES gera carry e borrow em instruções distintas - o borrow é
o da última operação de SUB, e o carry e o da última operação de ADD ou de
deslocamento. Assim, no AHMES, os indicadores de carry e borrow (C e B) não
possuem nenhuma relação entre si.

5 . 5 . 2 Aritmética de inteiros positivos


Apesar de projetado para trabalhar com números em complemento de dois, AHMES (e
também todos os processadores que utilizam complemento de dois) também pode manipular
números inteiros positivos. As operações de adição e subtração (instruções ADD e SUB)
podem ser utilizadas sem restrições ou modificações, mas os códigos de condição devem ser
analisados de forma diversa, conforme indicado a seguir:
1. O código de sinal (N) não tem mais significado.
2. O código de overflow (V) também perde o significado e não deve ser utilizado.

3. Os códigos de carry (C) e borrow (B) mantêm o seu significado original, e passam
adicionalmente a indicar também estouro de representação. Isto significa que a
indicação de carry após uma soma (C=1 após ADD) e borrow após uma subtração
(B=1 após SUB) são sinônimos de estouro de representação.
4. O código de zero (Z) mantém seu significado.

5 . 5 . 3 Aritmética em complemento de um
A aritmética em complemento de um é em si bem semelhante à de complemento de dois; a
diferença básica está na representação de número negativos, que possuem a quantidade –1 a
mais. Isto permite que um computador projetado com aritmética em complemento de dois
também possa manipular, com relativa facilidade, números em complemento de um.
Conforme visto na seção 2.3.3, a soma de dois números em complemento de um requer uma
eventual correção do resultado, através da soma do carry:

0 LDA 128 % primeiro operando está na posição 128


2 ADD 129 % segundo operando está na posição 129
4 JNC 8 % se não houve carry, resultado está correto
6 ADD 130 % posição 130 contém a constante 1
8HLT % resultado está no acumulador

De maneira análoga, a subtração de dois números em complemento de um também requer


uma eventual correção do resultado, subtraindo-se o borrow se este for um:

0 LDA 128 % primeiro operando está na posição 128


2 SUB 129 % segundo operando está na posição 129

5-6
4 JNB 8 % se não houve borrow, resultado está
correto
6 SUB 130 % posição 130 contém a constante 1
8HLT % resultado está no acumulador

Os códigos de condição devem ser interpretados de maneira um pouco diversa:


1. O código de zero (Z) somente detecta o zero positivo (00000000); o zero negativo
(11111111) deve ser testado à parte e convertido para o zero positivo.

2. Os códigos de sinal (N), carry (C), borrow (B) e overflow (V) mantêm o seu
significado original, mas devem ser analisados após a correção do resultado, ou seja,
após as duas operações de ADD ou SUB.

5 . 5 . 4 Aritmética em sinal/magnitude

Um computador projetado para aritmética em complemento de dois não manipula facilmente


números em sinal/magnitude. As operações de soma e subtração necessitam de grandes
ajustes para serem efetuadas; não basta o simples uso das instruções de ADD e SUB. Além
disto, os códigos de condição não são de grande ajuda:
1. O código de zero (Z) somente detecta o zero positivo (00000000); o zero negativo
(10000000) deve ser testado a parte.
2. O código de overflow (V) não tem significado e não deve ser utilizado.
3. Os códigos de carry (C) e borrow (B) também perdem seu significado original.

4. O código de sinal (N) mantém seu significado e pode ser utilizado.


Basicamente o bit de sinal e os bits de magnitude devem ser isolados e tratados
separadamente. Para esta separação, podem ser utilizadas máscaras, como indicado na rotina
abaixo:
LDA 128 % operando está na posição 128
AND 131 % posição 131 contem 10000000 (para isolar o sinal)
STA 129 % posição 129 recebe o sinal
LDA 128 % carrega novamente o operando
AND 132 % posição 132 contem 01111111 (para isolar a magnitude)
STA 130 % posição 130 recebe os sete bits da magnitude

Uma vez isolados, sinal e magnitude podem ser manipulados individualmente, conforme a
tabela 2.4 do capítulo 2, seção 2.3.2. Um eventual estouro de representação pode ser
detectado através do oitavo bit da magnitude (o bit de sinal do AHMES). Se este bit for
ligado após uma soma, isto indica que a magnitude necessita de oito bits para ser
representada, ou seja, não pode mais ser representada somente com sete bits.
Após a realização da operação desejada, os bits de sinal e magnitude devem ser novamente
reunidos. Assumindo-se a mesma ocupação de memória do trecho de programa anterior,
tem-se a seguinte rotina:
LDA 130 % carrega a magnitude (oitavo bit em zero)
OR 129 % inclui o bit de sinal (no oitavo bit; demais estão em zero)
STA 128 % armazena o operando na posição 128

5-7
Capítulo
SEIS
Multiplicação e Divisão

6 . 1 Multiplicação binária (números inteiros positivos)

Em qualquer sistema de numeração, a multiplicação é uma forma resumida da soma repetitiva


de uma parcela consigo mesma (o multiplicando) um determinado número de vezes (dado
pelo multiplicador). Assim, a multiplicação ‘a x b’ significa ‘a + a + a + ... + a’, onde a
parcela ‘a’ se repete ‘b’ vezes. A soma repetitiva levaria entretanto muito tempo para ser
realizada, especialmente para números grandes. Assim, desenvolveu-se (para o sistema
decimal) um método que permitisse realizar a multiplicação de forma mais rápida. Cada
dígito do multiplicador multiplica individualmente o multiplicando e depois as parcelas
intermediárias são somadas. Deve-se notar que cada parcela, além de ser multiplicada pelo
dígito do multiplicador, também é multiplicada pelo peso deste dígito:

63
123
189 (63 x 3 x 1)
1260 (63 x 2 x 10)
+ 6300 (63 x 1 x 100)
7749
Para simplificar a notação, assume-se o peso do dígito implicitamente, deixando-se de
escrever os respectivos zeros. No lugar disto, simplesmente desloca-se cada parcela uma
casa decimal para esquerda:
63
123
189 (sem deslocamento: x 1)
126 (um deslocamento: x 10)
+ 63 (dois deslocamentos: x 100)
7749
Esta é a forma normal de realização da multiplicação decimal. A multiplicação binária é
realizada exatamente da mesma forma. Observe-se que um deslocamento para a esquerda, em
qualquer sistema de numeração, equivale a multiplicar pela base deste sistema. Assim, em
decimal, para multiplicar por dez simplesmente desloca-se todo o número uma casa para a
esquerda e acrescenta-se um ‘zero’ na posição menos significativa. O mesmo vale para o
sistema binário quando se quiser multiplicar por dois.

Em binário a tabela de multiplicação é extremamente simples. Note-se que, ao multiplicar-se


por zero, o resultado é zero, e ao multiplicar-se um número qualquer por um, o resultado é
este próprio número.
Multiplicando Multiplicador Resultado
0 0 0
0 1 0
1 0 0
1 1 1

6-1
A multiplicação segue o método da multiplicação decimal: analisa-se cada dígito do
multiplicador, da esquerda para direita; multiplica-se o multiplicando por cada um destes
dígitos; desloca-se o resultado intermediário de cada multiplicação cada vez uma casa a mais
para a esquerda; e finalmente soma-se todas as parcelas, formando o resultado final. Por
exemplo, sejam dois números de seis bits, em representação inteira positiva:

1 1 0 1 1 0
x 1 1 0 0 1 1
1 1 0 1 1 0
1 1 0 1 1 0
0 0 0 0 0 0
0 0 0 0 0 0
1 1 0 1 1 0
+ 1 1 0 1 1 0
1 0 1 0 1 1 0 0 0 0 1 0

Quando um dígito do multiplicador for zero, o resultado da multiplicação intermediária


também será zero. Esta parcela pode então ser eliminada da soma final, pois não contribui
para o resultado:

1 1 0 1 1 0
x 1 1 0 0 1 1
1 1 0 1 1 0
1 1 0 1 1 0
1 1 0 1 1 0
+ 1 1 0 1 1 0
1 0 1 0 1 1 0 0 0 0 1 0
Para um computador, somar diversas parcelas é bem mais complexo do que somar duas
parcelas. Assim, as parcelas intermediárias são somadas a medida em que são formadas:
1 1 0 1 1 0
x 1 1 0 0 1 1
1 1 0 1 1 0
+ 1 1 0 1 1 0
1 0 1 0 0 0 1 0
+ 1 1 0 1 1 0
1 0 0 0 0 0 0 0 0 1 0
+ 1 1 0 1 1 0
1 0 1 0 1 1 0 0 0 0 1 0

No exemplo acima, multiplicou-se dois números de seis bits e obteve-se um resultado em 12


bits. De um modo geral, multiplicando-se um número de ‘n’ bits por outro de ‘m’ bits
obtém-se um resultado de ‘n+m’ bits. No caso dos computadores, que trabalham com
números de ‘n’ bits, a multiplicação produz números de ‘2n’ bits. Devido a este fato, nunca
ocorre estouro de representação. Para a multiplicação binária tem-se, até agora, o seguinte
método:

1. Início: i ← 0, resultado ← 0
2. Se o bit de ordem ‘i’ do multiplicador for zero, ir para 4.
3. Somar o multiplicando ao resultado (resultado ← resultado + multiplicando)
4. Deslocar o multiplicando para a esquerda (multiplicando ← multiplicando x 2)
5. Incrementar ‘i’ de uma unidade (i ← i + 1)
Se ‘i’ for menor que o número de bits do multiplicador, ir para 2.
Senão, terminar.

6-2
Devido ao fato de trabalhar com um número fixo de bits, não é tão simples para um
computador somar números de comprimentos diferentes. Assim, o método de multiplicação
ainda precisa ser melhor adaptado. Para tanto, devemos observar que:

• o bit menos significativo do resultado só é afetado pela primeira parcela da soma;


nenhuma outra parcela pode alterar mais este bit.
• generalizando, o ‘i-ésimo’ bit menos significativo do resultado só é afetado pelas
primeiras ‘i’ parcelas intermediárias. Por exemplo, o segundo bit do resultado só é
afetado pelas duas primeiras parcelas; o terceiro bit só é afetado pelas três primeiras
parcelas, e assim por diante.
• se uma das parcelas de uma soma deve ser deslocada para a esquerda, o mesmo
resultado é obtido deslocando-se a outra parcela para a direita.
Assim, a cada passo da multiplicação, no lugar de deslocar-se o multiplicando para a
esquerda, desloca-se o resultado para a direita. E os bits menos significativos do resultado,
um de cada vez, são armazenados em um elemento especial, pois não serão mais
modificados. Sejam então ‘M’ o multiplicando, em ‘n’ bits; ‘m’ o multiplicador, também em
‘n’ bits; ‘R’ os ‘n’ bits mais significativos do resultado; ‘r’ os ‘n’ bits menos significativos
do resultado; e ‘c’ o vai-um da soma (carry). Tem-se então o seguinte método:

1. Início: i ← 0, R ← 0, r ← 0
2. Se o bit de ordem ‘i’ de m for zero (mi=0), fazer c ← 0 e ir para 4.
3. Somar M a R (R ← R + M), c ← vai-um
4. Deslocar os ‘2n’ bits do resultado para direita, usando o ‘vai-um’ da soma
anterior como novo bit mais significativo do resultado (se não houve soma, usa-
se ‘0’):
( R r ) ← desl.direita( c R r )
5. Incrementar ‘i’ de uma unidade (i ← i + 1)
Se ‘i’ for menor que ‘n’, ir para 2. Senão, terminar.
Sejam por exemplo n = 6, M = 110110, m = 110011. Então tem-se:

1. i ← 0, R ← 000000, r ← 000000
2. m(0) = 1, continuar em 3
3. R ← 000000 + 110110 = 110110, c = 0
4. (R r) ← desl.direita(0 110110 000000) = (011011 000000)
5. i ← 1; i < 6; ir para 2
2. m(1) = 1, continuar em 3
3. R ← 011011 + 110110 = 010001, c = 1
4. (R r) ← desl.direita(1 010001 000000) = (101000 100000)
5. i ← 2; i < 6; ir para 2
2. m(2) = 0, c = 0 e ir para 4
4. (R r) ← desl.direita( 0 101000 100000) = (010100 010000)
5. i ← 3; i < 6; ir para 2
2. m(3) = 0, c = 0 e ir para 4
4. (R r) ← desl.direita(0 010100 010000) = (001010 001000)
5. i ← 4; i < 6; ir para 2
2. m(4) = 1, continuar em 3
3. R ← 001010 + 110110 = 000000, c= 1
4. (R r) ← desl.direita(1 000000 001000) = (100000 000100)
5. i ← 5; i < 6; ir para 2

6-3
2. m(5) = 1, continuar em 3
3. R ← 100000 + 110110 = 010110, c = 1
4. (R r) ← desl.direita(1 010110 000100) = (101011 000010)
5. i ← 6; fim. Resultado = 101011000010

O método pode ser melhorado ainda mais. Basta observar-se os seguintes pontos:
1. A rigor, ‘r’ não necessita ser inicializado com zeros. (No exemplo anterior, basta
substituir o ‘000000’ inicial de ‘r’ por ‘xxxxxx’)
2. Uma vez analisado, um bit do multiplicador não será mais utilizado (não influencia
mais no resultado) e pode ser descartado.
3. Considerando-se o item (2), para facilitar a análise dos bits do multiplicador, este
também pode ser deslocado para a direita no passo 4. Assim, a análise será sempre no
bit menos significativo do multiplicador.
4. Desta maneira, a variável ‘i’ somente tem a função de controlar que o laço seja
executado ‘n’ vezes. Assim, ao invés de contar para cima de zero até ‘n’, pode-se
também contar para baixo de ‘n’ até zero (Observe-se que um teste de zero é bem
simples de ser realizado em um computador).

O algoritmo modificado em função destas observações pode ser visto a seguir. Sejam ‘m’,
‘M’, ‘R’, ‘r’, ‘n’ e ‘c’ definidos como anteriormente.

1. Início: i ← ‘n’, R ← 0
2. Deslocar m para a direita, juntamente com o carry. Note-se que o bit menos
significativo de m é levado para o carry:
( m c ) ← desl.direita( m )
Se c = 0, ir para 4.
3. Somar M a R (R ← R + M), c ← vai-um
4. Deslocar os ‘2n’ bits do resultado para direita, usando o ‘vai-um’ da soma
anterior como novo bit mais significativo do resultado (se não houve soma, usa-
se ‘0’ devido ao passo 2):
( R r ) ← desl.direita( c R r )
5. Decrementar ‘i’ de uma unidade (i ← i – 1)
Se ‘i’ não for zero, ir para 2. Senão, terminar. Resultado em (R r)
O resultado é expresso em ‘2n’ bits. Caso seja desejado um resultado em somente ‘n’ bits,
deve-se truncar os ‘n’ bits mais significativos. Neste caso, entretanto, deve-se testar por um
possível estouro de representação:

• Se os números forem representados como inteiros positivos, a truncagem é realizada


com sucesso (não há estouro) caso os ‘n’ bits mais significativos sejam todos iguais a
zero.
• Se os números forem representados como inteiros em complemento de dois (mas
positivos), a truncagem é realizada com sucesso (não há estouro) caso os ‘n+1’ bits
mas significativos sejam todos zero. Ou seja, os ‘n’ bits mais significativos devem ser
zeros para poderem ser desprezados, e o bit de sinal dos ‘n’ bits restantes deve ser zero
para o resultado ser positivo.

Para se adaptar às instruções de deslocamento para a direita do Ahmes, o algoritmo deve ser
um pouco modificado. No passo 2, deve ser usada a instrução de SHR (bit mais
significativo recebe zero, e o bit menos significativo vai para o carry). No passo 4, o
deslocamento conjunto de c, R e r deve ser desdobrado em duas instruções de ROR (bit mais
significativo recebe o valor atual do carry, e o bit menos significativo vai para o carry). Com
isto, o algoritmo fica:

6-4
1. Início: i ← ‘n’, R ← 0
2. Deslocar ‘m’ para direita (o bit menos significativo vai para o carry). Se o carry
for zero (c=0), ir para 4.
3. Somar M a R (R ← R + M), c ← vai-um
4. Deslocar os ‘2n’ bits do resultado para direita, usando o ‘c’ como novo bit mais
significativo do resultado: ( R m ) ← desl.direita( c R m )
4.1. Deslocar R para direita (carry recebe o bit menos significativo).
4.2. Deslocar r para direita (carry torna-se o novo bit mais significativo).
5. Decrementar ‘i’ de uma unidade (i ← i – 1)
Se ‘i’ não for zero, ir para 2. Senão, terminar. Resultado em (R m).
Para a implementação do algoritmo acima, assume-se o seguinte mapeamento de memória
para as variáveis e constantes:
multiplicando M: endereço 128 constante 0: endereço 134
multiplicador m: endereço 129 constante 1: endereço 135
resultado R: endereço 130 constante 8: endereço 136
resultado r: endereço 131
contador: endereço 132
multiplicador deslocado: endereço 133

Endereço Instrução Comentários


0 LDA 136
2 STA 132 Inicializa contador com ‘n’ (oito no caso)
4 LDA 134
6 STA 130 Inicializa R com zero
8 LDA 129
10 STA 133 Inicializa multiplicador com m
12 LDA 133 Início do laço: Carrega multiplicador em AC
14 SHR Desloca m; carry recebe o bit menos significativo
15 STA 133 Salva o multiplicador
17 JNC 25 Se não houve carry, vai para o deslocamento
19 LDA 130 Carrega R em AC
21 ADD 128 Soma R + M; resultado parcial no acumulador
23 STA 130 Salva o resultado parcial
25 LDA 130 Carrega resultado parcial (bits mais significativos) em AC
27 ROR Desloca para direita com carry
28 STA 130 Salva o resultado deslocado
30 LDA 131 Carrega resultado parcial (bits menos significativos) em AC
32 ROR Desloca para direita com carry
33 STA 131 Salva o resultado deslocado
35 LDA 132 Carrega o contador em AC
37 SUB 135 Decrementa de um
39 STA 132 Salva o contador
41 JNZ 12 Se não for zero, executa mais uma vez o laço
43 HLT Fim; resultado em R e m
Esta implementação pode ser otimizada, se os seguintes itens forem observados:
1. Depois do teste do carry (instrução JNC, no endereço 17), os dois ramos a serem
seguidos (endereços 25 e 19) iniciam com a mesma instrução (LDA 130).
2. Como a instrução LDA não altera o carry, ela pode ser deslocada para antes do teste
do carry. Com isto, em vez de duas instruções de LDA, usa-se apenas uma.
3. Como após a soma sobre R (endereços 19 a 23) segue-se o deslocamento de R
(endereços 25 a 28), pode-se eliminar também a instrução STA 130 do endereço 23.
Assim, a implementação fica (mantendo-se o mesmo mapeamento de variáveis e constantes):

6-5
Endereço Instrução Comentários
0 LDA 136
2 STA 132 Inicializa contador com ‘n’ (oito no caso)
4 LDA 134
6 STA 130 Inicializa R com zero
8 LDA 129
10 STA 133 Inicializa multiplicador com m
12 LDA 133 Início do laço: Carrega multiplicador em AC
14 SHR Desloca m; carry recebe o bit menos significativo
15 STA 133 Salva o multiplicador
17 LDA 130 Carrega resultado parcial (bits mais significativos) em AC
19 JNC 23 Se não houve carry (no SHR), vai para o deslocamento
21 ADD 128 Soma R + M; resultado parcial no acumulador
23 ROR Desloca R para direita com carry
24 STA 130 Salva o resultado deslocado
26 LDA 131 Carrega resultado parcial (bits menos significativos) em AC
28 ROR Desloca para direita com carry
29 STA 131 Salva o resultado deslocado
31 LDA 132 Carrega o contador em AC
33 SUB 135 Decrementa de um
35 STA 132 Salva o contador
37 JNZ 12 Se não for zero, executa mais uma vez o laço
39 HLT Fim; resultado em R e r
Uma otimização em termos de variáveis pode ser tentada, observando-se que:
1. Os bits do multiplicador são analisado da direita para esquerda. Isto é realizado
trazendo-se os seus bits para os bits menos significativos. Com isto os bits mais
significativos vão sendo liberados, um de cada vez.
2. Os bits menos significativos do resultado parcial vão sendo deslocados para ‘r’ pela
esquerda, ou seja, os bits são ocupados da esquerda para a direita, um de cada vez.
3. Assim, a medida que os bits menos significativos de ‘m’ vão sendo analisados, os
bits mais significativos de ‘r’ vão sendo formados. Assim, pode-se utilizar a mesma
palavra de memória (ou variável) para ‘r’ e ‘m’.
Com estas observações, pode-se desenvolver o método final para a multiplicação de dois
números binários, representados como inteiros positivos (este método vale também para
números positivos em complemento de dois). Sejam ‘m’, ‘M’, ‘R’ e ‘n’ definidos como
anteriormente. A variável ‘i’ tem a função de controlar que o laço seja executado ‘n’ vezes
(de ‘n’ até zero). A variável ‘r’ não é mais utilizada; sua função é realizada por ‘m’.

O algoritmo deve ser modificado, para se adaptar ao duplo uso de ‘m’ e ‘r’. A variável ‘m’ é
deslocada no início do laço, e ‘r’ é deslocada somente no fim. Na implementação abaixo,
optou-se por deslocar ‘m’ no início da laço, e somente ajustar o bit mais significativo de ‘r’
através de uma instrução de OR e uma máscara adequada (constante 128). Com isto, o
algoritmo fica:

1. Início: i ← ‘n’, R ← 0
2. Deslocar ‘m’ para direita (o bit menos significativo vai para o carry). Se o carry
for zero (c=0), ir para 4.
3. Somar M a R (R ← R + M), c ← vai-um
4. Deslocar os ‘2n’ bits do resultado para direita, usando o ‘c’ como novo bit mais
significativo do resultado: ( R m ) ← desl.direita( c R m )
4.1. Deslocar R para direita (carry recebe o bit menos significativo).
4.2. Testar o carry (que contem o bit menos significativo de R). Se for zero, não
há nada para fazer (o bit mais significativo de m já é zero, devido ao passo 2). Se
for um, ligar o bit mais significativo de m.

6-6
5. Decrementar ‘i’ de uma unidade (i ← i – 1)
Se ‘i’ não for zero, ir para 2. Senão, terminar. Resultado em (R m).
Para a implementação do algoritmo acima, assume-se o mesmo mapeamento de memória dos
algoritmos anteriores, mas adaptado para este último algoritmo.

multiplicando M: endereço 128 constante 0: endereço 134


multiplicador m: endereço 129 constante 1: endereço 135
resultado R: endereço 130 constante 8: endereço 136
resultado r: endereço 131 constante 128: endereço 137
contador: endereço 132

Endereço Instrução Comentários


0 LDA 136
2 STA 132 Inicializa contador com ‘n’ (oito no caso)
4 LDA 134
6 STA 130 Inicializa R com zero
8 LDA 129
10 STA 131 Inicializa multiplicador com m
12 LDA 131 Início do laço: Carrega multiplicador em AC
14 SHR Desloca m; carry recebe o bit menos significativo
15 STA 131 Salva o multiplicador
17 LDA 130 Carrega resultado parcial (bits mais significativos) em AC
19 JNC 23 Se não houve carry (no SHR), vai para o deslocamento
21 ADD 128 Soma R + M; resultado parcial no acumulador
23 ROR Desloca R para direita; carry recebe bit menos significativo
24 STA 130 Salva o resultado deslocado
26 JNC 34 Se carry é zero, bits menos significativos (m) estão OK
28 LDA 131 Carrega resultado parcial (bits menos significativos) em AC
30 OR 137 Liga o bit mais significativo do resultado (m)
32 STA 131 Salva o resultado deslocado
34 LDA 132 Carrega o contador em AC
36 SUB 135 Decrementa de um
38 STA 132 Salva o contador
40 JNZ 12 Se não for zero, executa mais uma vez o laço
42 HLT Fim; resultado em R e m

6 . 2 Multiplicação binária (números em complemento de dois)

A multiplicação de números com sinal, em complemento de dois, usa o mesmo método


básico, com três modificações (duas no método e uma na realização da truncagem para ‘n’
bits):

1. Repete-se ‘n–1’ vezes o teste de bit, soma condicional e deslocamento. Na última


passagem do laço (na n-ésima vez), executa-se uma subtração condicional no lugar de
uma soma. Isto ocorre porque, em complemento de dois, o bit mais significativo
(sinal) tem peso negativo.
2. No deslocamento para a direita, deve-se agora manter (ou corrigir) o sinal do
resultado intermediário. Para isto, não se desloca mais com o vai-um da soma (ou
zero). Devem ser observadas as regras:
•se não houve soma (ou subtração) condicional anterior, desloca-se para a direita
duplicando-se o bit de sinal, isto é, o bit de sinal é deslocado para a direita, e o
novo bit de sinal é igual ao anterior.
•se houve soma (ou subtração) anterior, mas não ocorreu estouro, desloca-se para
a direita duplicando-se o bit de sinal.
•se houve soma (ou subtração) anterior, e ocorreu estouro, deslocam-se todos os
bits para a direita (inclusive o bit de sinal), e o novo bit de sinal é o inverso do

6-7
sinal anterior. Com isto corrige-se o erro na representação do resultado
introduzido pelo estouro.
3. Na truncagem para ‘n’ bits, deve-se observar que ela é agora possível em dois casos:
•Se os ‘n+1’ bits mais significativos forem zeros. Neste caso, tem-se um número
positivo em ‘2n’ bits que pode ser representado em ‘n’ bits, sem estouro.
•Se os ‘n+1’ bits mais significativos forem todos uns. Neste caso, tem-se um
número negativo em ‘2n’ bits que pode ser representado em ‘n’ bits, sem
estouro.
Nos demais casos, não é possível truncar o resultado para ‘n’ bits sem que haja
estouro de representação.

A implementação necessita de um teste de overflow após a soma, o que já está presente no


Ahmes. Dependendo do sinal do resultado parcial e do overflow, tem-se quatro casos a
tratar:

1. Resultado positivo, sem overflow (N=0, V=0). Neste caso, basta deslocar o resultado
através de uma instrução de SHR (que automaticamente coloca um zero no bit mais
significativo, mantendo assim o sinal).
2. Resultado positivo, com overflow (N=0, V=1). Neste caso, desloca-se o resultado
através de uma instrução de SHR e depois realiza-se um OR com a máscara binária
10000000 (decimal 128), para tornar o resultado negativo.
3. Resultado negativo, sem overflow (N=1, V=0). Este caso é o mesmo do item (2)
acima. Deve-se deslocar o resultado com um SHR e depois ligar o bit de sinal com um
OR com a máscara binária 10000000.
4. Resultado negativo, com overflow (N=1, V=1). Neste caso, como no caso (1) acima,
basta deslocar o resultado através de uma instrução de SHR (que coloca um zero no
bit mais significativo, tornando assim o resultado positivo).
Estes quatro casos referem-se simplesmente ao deslocamento dos bits mais significativos do
resultado. Considerando-se a necessidade de identificar o caso e tratá-lo adequadamente, e
repetir a análise separadamente para o último passo (que utiliza subtração condicional em vez
de soma), a programação deste algoritmo em Ahmes seria bem extensa e complexa. Para o
Ahmes, seria mais simples converter os operandos para números positivos, multiplicá-los e
depois ajustar o sinal o resultado de acordo com os sinais dos operandos originais.
Um outro método para multiplicação binária para números em complemento de dois foi
desenvolvido por Booth, e é mais rápido que os métodos descritos acima. Este algoritmo
pode ser encontrado no livro Computer Architecture and Organization, de John P. Hayes, na
seção 3.3.2.

6 . 3 Divisão binária (números inteiros positivos)

Em qualquer sistema de numeração, a divisão é a operação inversa da multiplicação, ou seja,


seu resultado (quociente) indica quantas vezes podemos subtrair um número (divisor) de
outro (dividendo) de forma que ainda reste um número positivo ou zero (resto). Na divisão
em decimal, realizam-se exatamente os passos contrários da multiplicação. Os dígitos do
resultado são calculados da esquerda para direita, e a cada passo determina-se um dígito do
quociente:

7749 ÷ 63
–63 123
144 (7749 – 6300 = 1449)
–126
189 (1449 – 1260 = 189)
–189
0 (189 – 189 = 0)

6-8
Note-se que, como na multiplicação, o peso de cada casa é assumido implicitamente, e os
zeros respectivos não são escritos. Assim, no lugar de 6300 (63 x 1 x 100) anota-se
simplesmente 63 (63 x 1); no lugar de 1260 (63 x 2 x 10) escreve-se 126 (63 x 2). A
divisão apresenta uma dificuldade adicional para sua realização pelo método esboçado acima:
a necessidade de “estimar” o dígito que deve ser multiplicado pelo divisor para obter a maior
parcela possível de ser subtraída do dividendo.
Na divisão binária, utiliza-se exatamente o mesmo método, mas o processo de “adivinhação”
se torna bem mais fácil: só é necessário verificar se o divisor pode ser subtraído dos dígitos
mais significativos do dividendo (bit = 1) ou não (bit = 0).

1 0 1 0 1 1 0 0 0 0 1 0 ÷ 1 1 0 0 1 1
– 1 1 0 0 1 1 1 1 0 1 1 0
1 0 0 0 1 1 0
– 1 1 0 0 1 1
0 1 0 0 1 1 0
1 0 0 1 1 0 0
– 1 1 0 0 1 1
0 1 1 0 0 1 1
– 1 1 0 0 1 1
0 0 0 0 0 0
0 0 0 0 0 0
Note-se que, como no sistema decimal, depois de cada subtração (ou tentativa mal sucedida
de subtração, se o dígito calculado para o quociente for zero) um novo dígito do dividendo é
incorporado à análise, isto é, um novo dígito do dividendo é “baixado” para o resultado da
subtração. E, também como no sistema decimal, o número de bits do dividendo que
participam da primeira análise é escolhido de tal forma que formem um número maior que o
divisor, isto é, que a primeira subtração seja possível.
Com um dividendo de ‘m’ bits e um divisor de ‘n’ bits obtém-se um quociente de ‘m–n’ bits
e um resto de ‘n’ bits. A divisão em um computador, sendo a operação inversa da
multiplicação, opera com um dividendo de ‘2n’ bits, um divisor de ‘n’ bits e fornece
quociente e resto em ‘n’ bits. Ao contrário da multiplicação, entretanto, onde o resultado
sempre é representável sem estouro, na divisão o quociente pode não ser representável em
‘n’ bits. Nestes casos sinaliza-se estouro de representação. Nos casos em que o dividendo
tem somente ‘n’ bits, ele deve ser inicialmente expandido para ‘2n’ bits, incluindo-se ‘n’
zeros à esquerda do dividendo.
Para divisão de dois números representados como inteiros positivos usa-se o método abaixo,
onde o dividendo ‘D’ tem ‘2n’ bits e o divisor ‘v’ tem ‘n’ bits. Note-se que, nesta
representação não existem números negativos. Assim, um teste comparativo deve ser feito
para verificar se a subtração é possível, ou seja, se o resultado será um número positivo.
Observe-se também que sua aplicação restringe-se aos números naturais sem o ‘zero’. A
inclusão do ‘zero’, obrigaria ao teste do valor do divisor, abortando a operação se esta for
uma tentativa de divisão por zero.

1. Início: formar ‘r’ com os ‘n+1’ bits mais significativos do dividendo ‘D’, i ← ‘n’.
2. Se r ≥ v, então subtrair r ← r–v e colocar 1 no bit menos significativo de q
(quociente). Senão, somente colocar 0 no bit menos significativo de ‘q’. Obs: neste
passo, usar somente ‘n’ bits para representar ‘r’.
3. Decrementar i (i ← i–1). Se i=0, encerrar. O resto está em ‘r’ e o quociente em ‘q’.
4. Deslocar ‘r’ para esquerda, mantendo o bit mais significativo (ou seja, representar
‘r’ com ‘n+1’ bits). Como novo bit menos significativo, utilizar o bit seguinte do
dividendo original (“baixar um bit”). Deslocar ‘q’ também para a esquerda. Ir para
o passo 2.

Exemplo: sejam D = 101011000010 e v = 110011. Então tem-se:

6-9
1. r ← 1010110, i ← 6
2. r ≥ v (1010110 ≥ 110011). Então r ← 1010110 – 110011 = 100011 e q ← 1
3. i ← 5
4. r ← 1000110, q ← 1x
2. r ≥ v (1000110 ≥ 110011). Então r ← 1000110 – 110011 = 010011 e q ← 11
3. i ← 4
4. r ← 0100110, q ← 11x
2. r < v (0100110 < 110011). Então r ← 100110 e q ← 110
3. i ← 3
4. r ← 1001100, q ← 110x
2. r ≥ v (1001100 ≥ 110011). Então r ← 1001100 – 110011 = 011001 e q ← 1101
3. i ← 2
4. r ← 0110011, q ← 1101x
2. r ≥ v (0110011 ≥ 110011). Então r ← 0110011 – 110011 = 000000 e q ← 11011
3. i ← 1
4. r ← 0000000, q ← 11011x
2. r < v (0000000 < 110011). Então r ← 000000 e q ← 110110
3. i ← 0, encerrar. Quociente = 110110 e resto = 0

Para alguns operandos, pode ocorrer que quando for executado o passo 2, na primeira vez, o
resultado da operação r ← r–v seja maior do que ‘v’ (ou até mesmo igual a ‘v’). Nestes
casos, o algoritmo não é adequado para realização da operação (portanto, a divisão não
poderá ser realizada por este método: será necessário escolher algum outro). Para o uso
adequado deste algoritmo, é interessante incluir uma observação no passo 2 de verificação do
resultado da operação r ← r–v. Se após esta operação obter-se r ≥ v, então deve-se abortar a
operação, pois o quociente exigiria mais de ‘n’ bits para ser representado.
A situação explicada acima é exemplificada com a divisão de 11010100 por 1011. Para este
exemplo, D = 11010100 e v = 1011. Então tem-se:
1. r ← 11010, i ← 4
2. r ≥ v (11010 ≥ 1011). Então r ← 11010 – 1011 = 1111 e q ← 1
Neste passo, se obtém um valor de ‘r’ que é maior do que o divisor (o que não faz sentido).
O prosseguimento da operação pelo algoritmo faria com que se obtenha uma resposta
incorreta.
Como poderia ser corrigido o algoritmo sem modificá-lo demasiadamente? Se, para iniciar a
operação, utilizar-se apenas n bits do dividendo (D), o problema se resolve a nível do passo
2. Mas agora será necessária uma interação a mais (é preciso “baixar” mais um bit de D),
então i deve ser aumentado em uma unidade. Entretanto esta é uma solução para este caso e
não universal. Experimente mudar o divisor para 0101 e observe o que ocorre. O tipo de
análise e a visão humana sobre as variáveis não são facilmente “transportáveis” para o
computador, o que vai exigir a alteração do algoritmo para implementação na máquina.
Com isto, o algoritmo fica:

1. Início: Se v for zero, terminar indicando erro de divisão por zero. Senão, calcular D
– v. Se carry=1 (borrow=0), então D ≥ v e terminar indicando estouro. Se não,
então a divisão pode ser realizada.

6-10
i ← ‘n’. Formar ‘r’ com os ‘n+1’ bits mais significativos do dividendo.
2. Se r ≥ v, então subtrair r ← r–v e colocar 1 no bit menos significativo de q
(quociente). Senão, somente colocar 0 no bit menos significativo de ‘q’. Obs: neste
passo, usar somente ‘n’ bits para representar o resultado ‘r’.
3. Decrementar i (i ← i–1). Se i=0, encerrar. O resto está em ‘r’ e o quociente em ‘q’.
4. Deslocar ‘r’ para esquerda, mantendo o bit mais significativo (ou seja, representar
‘r’ com ‘n+1’ bits). Como novo bit menos significativo, utilizar o bit seguinte do
dividendo original (“baixar um bit”). Deslocar ‘q’ também para a esquerda. Ir para
o passo 2.

O algoritmo requer que se trabalhe com os ‘n+1’ bits mais significativos do dividendo. Isto
pode ser implementado realizando-se um deslocamento para a esquerda do dividendo, de
forma que o carry contenha o bit mais significativo e ‘D’ contenha os ‘n’ bits seguintes.
Assim, a comparação é entre ‘r’ (carry e D) e ‘v’. Se o carry for um, então garantidamente
‘r’ > ‘v’. Se o carry for zero, então calcula-se ‘D’–‘v’. Se o borrow desta operação for zero
(borrow=0), então ‘r’≥‘v’. Se o borrow for um (borrow=1) então ‘r‘<‘v‘.
Com estas considerações, o algoritmo fica:

1. Início: Se v for zero, terminar indicando erro de divisão por zero. Senão, verificar
se D ≥ v. Se sim, ocorrerá estouro no quociente. Neste caso, terminar indicando
estouro. Se não, então a divisão pode ser realizada.
2. Início: i ← n, q ← 0.
3. Deslocar o quociente q para esquerda, preparando-o para receber o novo bit menos
significativo. Deslocar D e d para esquerda (bit mais significativo de D vai para o
carry). Testar D e carry contra v. Se carry =1, então D > v. Senão, calcular D – v.
Se houver borrow, então D<v. Senão, D≥v. No caso de D≥v, subtrair D ← D–v e
colocar 1 no bit menos significativo de q (quociente). Senão, deixar D inalterado e
colocar 0 no bit menos significativo de q (como o deslocamento para esquerda já
inseriu um zero, basta deixar q inalterado).
4. Decrementar i (i ← i–1). Se i=0, encerrar. O resto está em ‘D’ e o quociente em
‘q’. Senão, ir para o passo 3.
No algoritmo para o AHMES, assume-se que o dividendo esteja nos endereços 128 (D - bits
mais significativos) e 129 (d - bits menos significativos), o divisor v no endereço 130, o
quociente q deve ser colocado em 131 e o resto r em 132. O estado da divisão é indicado na
posição 133 (-1 indica estouro na divisão, 0 indica divisão por zero e 1 indica divisão
normal). Para contador será utilizada a posição 134.

Para as constantes, são utilizadas as seguintes posições: 135 para zero, 136 para um, 137
para menos um (255) e 138 para oito.

Para deixar os valores de ‘D’ e ‘d’ inalterados, os seus valores serão copiados para as
posições 139 e 140, respectivamente. Sobre estas posições é que serão efetuados os
deslocamentos.
Endereço Instrução Comentários
0 LDA 130 Carrega o divisor
2 JZ 77 Se for zero, termina
4 LDA 128 Carrega D em A
6 SUB 130 Calcula D – v
8 JNB 81 Se borrow=0, então D ≥ v, e irá ocorrer estouro
10 LDA 128 Tudo bem. Divisão pode ser realizada
12 STA 139 Salva D em endereço temporário
14 LDA 129 Carrega d
16 STA 140 Salva d em endereço temporário
18 LDA 138 Obtém a constante 8

6-11
20 STA 134 Inicializa contador com ‘n’ (8 no caso)
22 LDA 135 Obtém a constante zero
24 STA 131 Inicializa q com zero
26 LDA 131 Carrega q
28 SHL Desloca q para esquerda
29 STA 131 Salva q
31 LDA 140 Carrega d
33 SHL Desloca d para esquerda (carry recebe bit mais significativo)
34 STA 140 Salva d
36 LDA 139 Carrega D
38 ROL Desloca D para esquerda (com carry)
39 STA 139 Salva D
41 JC 49 Testa o carry (antigo bit mais significativo de D)
43 LDA 139 Carry de D = 0, carrega D
45 SUB 130 Testa se D – v
47 JB 61 Testa o borrow; se 1, então D < v, nada a fazer
49 LDA 139 Borrow=0, então D > v
51 SUB 130 Atualiza D com D–v
53 STA 139 Atualiza D
55 LDA 131 Carrega q
57 OR 136 Coloca bit do quociente em 1
59 STA 131 Salva q
61 LDA 134 Carrega contador
63 SUB 136 Decrementa contador de laço
65 STA 134 Salva contador
67 JNZ 26 Se não for zero, termina
69 LDA 139 Obtém o D final
71 STA 132 Salva como resto
73 LDA 136 Sinaliza fim normal
75 JMP 83
77 LDA 135 Sinaliza divisão por zero
79 JMP 83
81 LDA 137 Sinaliza estouro na divisão
83 STA 133 Armazena indicador de estado
85 HLT Fim
O algoritmo acima pode ser otimizado observando-se que:
1. Enquanto os bits de ‘d’ vão sendo deslocados para a esquerda, os bits de ‘q’ vão
sendo formados a partir da direita. Assim, de maneira análoga ao caso da
multiplicação, para ‘d’ e ‘q’ pode ser utilizada uma só variável e em consequencia
uma só posição de memória. Assim, os endereços 140 e 131 são reunidos,
eliminado-se o 140.
2. Da variável ‘D’ vão sendo realizadas subtrações sucessivas, e no final do algoritmo
o ‘D’ final é o resto. Assim, para ‘D’ e ‘r’ pode-se utilizar somente uma variável.
Assim, dos endereços 139 e 132 elimina-se a necessidade do 139.

Com estas considerações, o algoritmo fica:

1. Início: Se v for zero, terminar indicando erro de divisão por zero. Senão, verificar
se D ≥ v. Se sim, ocorrerá estouro no quociente. Neste caso, terminar indicando
estouro. Se não, então a divisão pode ser realizada.
2. Início: i ← n, q ← d, r ← D.
3. Deslocar r e q (ou seja, D e q) para esquerda (bit mais significativo de r vai para o
carry). Testar r e carry contra v. Se carry =1, então r > v. Senão, calcular r – v. Se
houver borrow, então r<v. Senão, r≥v. No caso de r≥v, subtrair r ← r–v e colocar
1 no bit menos significativo de q. Senão, deixar D e q inalterados.

6-12
4. Decrementar i (i ← i–1). Se i=0, encerrar. O resto está em ‘r’ e o quociente em ‘q’.
Senão, ir para o passo 3.
No algoritmo para o AHMES, assume-se que o dividendo esteja nos endereços 128 (D - bits
mais significativos) e 129 (d - bits menos significativos), o divisor v no endereço 130, o
quociente q deve ser colocado em 131 e o resto r em 132. O estado da divisão é indicado na
posição 133 (-1 indica estouro na divisão, 0 indica divisão por zero e 1 indica divisão
normal). Para contador será utilizada a posição 134.
Para as constantes, são utilizadas as seguintes posições: 135 para zero, 136 para um, 137
para menos um (255) e 138 para oito.

Note-se que para ‘D’ temporário e ‘r’ é utilizada a mesma posição (132), assim como para
‘d’ temporário e o quociente ‘q’ é utilizada a mesma posição (131). A cópia de ‘D’ para ‘r’ é
feita no endereço 12 (STA 132), e a cópia de ‘d’para ‘q’ é feita na instrução do endereço 16
(STA 131).

Endereço Instrução Comentários


0 LDA 130 Carrega o divisor
2 JZ 64 Se for zero, termina
4 LDA 128 Carrega D em A
6 SUB 130 Calcula D – v
8 JNB 68 Se borrow=0, então D ≥ v, e irá ocorrer estouro
10 LDA 128 Tudo bem. Divisão pode ser realizada
12 STA 132 Salva D como o resto temporário (r)
14 LDA 129 Carrega d
16 STA 131 Salva d como quociente temporário (q)
18 LDA 138 Obtém a constante 8
20 STA 134 Inicializa contador com ‘n’ (8 no caso)
22 LDA 131 Carrega d (q)
24 SHL Desloca para esquerda (carry recebe bit mais significativo)
25 STA 131 Salva d (q)
27 LDA 132 Carrega D (r)
29 ROL Desloca para esquerda (com carry)
30 STA 132 Salva D (r)
32 JC 40 Testa o carry (antigo bit mais significativo de D(r))
34 LDA 132 Carry de r = 0, carrega r
36 SUB 130 Testa se r – v
38 JB 52 Testa o borrow; se 1, então r < v, nada a fazer
40 LDA 132 Borrow=0, então r > v
42 SUB 130 Calcula r–v
44 STA 132 Atualiza r
46 LDA 131 Carrega d (q)
48 OR 136 Coloca bit do quociente em 1
50 STA 131 Salva d (q)
52 LDA 134 Carrega contador
54 SUB 136 Decrementa contador de laço
56 STA 134 Salva contador
58 JNZ 22 Se não for zero, termina
60 LDA 136 Sinaliza fim normal
62 JMP 70
64 LDA 135 Sinaliza divisão por zero
66 JMP 70
68 LDA 137 Sinaliza estouro na divisão
70 STA 133 Armazena indicador de estado
72 HLT Fim

6-13
6 . 4 Divisão binária (números em complemento de dois, positivos)
Na representação em complemento de dois, é possível representar números negativos, e
assim o método discutido acima pode ser modificado. Não é mais necessário testar para
verificar se a subtração é possível. Simplesmente realiza-se a subtração e, se o resultado for
negativo, restaura-se o número original somando-se o divisor. Este é o método da “divisão
com restauração”.
Sejam ‘D’ o dividendo (com ‘2n’ bits) e ‘v’ o divisor (em ‘n’ bits). O quociente ‘q’ será
então representado por q n-1qn-2...q 2q1q0 em ‘n’ bits. O algorimo calcula um bit qi de cada
vez, da esquerda para a direita (‘i’ variando de ‘n-1’ até zero). A cada passo ‘i’ o divisor,
deslocado ‘i’ bits para a esquerda (representado por 2iv), é comparado com o resto parcial ri
(inicialmente, rn-1 é formado pelos bits mais significativos do dividendo). Se 2iv for menor
que ri, o bit qi do quociente é colocado em 0. Se 2iv for maior (ou igual) que ri, o bit qi do
quociente é colocado em 0, e um novo resto parcial é calculado pela operação
ri-1 ← ri – qi2iv
Nos métodos normalmente utilizados, é mais conveniente deslocar o resto parcial para
esquerda em relação a um divisor fixo. Assim, o divisor permanece sempre ‘v’, e o resto
parcial deslocado para a esquerda é representado por 2ri. Neste caso, a equação acima é
equivalente a
ri-1 ← 2ri – qiv

Quando se realiza uma subtração tentativa, calcula-se ri-1 ← 2ri – v. Esta operação já calcula
o novo resto parcial ri-1 quando 2ri – v for positivo, isto é, quando qi = 1. Se entretanto 2ri –
v for negativo, tem-se q i = 0 e o novo resto parcial ri-1 será igual ao anterior, ou seja, 2ri.
Este resto ri-1 pode neste caso ser obtido somando-se ‘v’ de volta ao resultado da subtração.
Esta é a base do método da divisão com restauração. A cada passagem a operação
ri-1 ← 2ri – v
é realizada. Quando o resultado é negativo, uma adição restauradora é necessária:
ri-1 ← ri-1 + v
ri-1 ← (2ri – v) + v = 2r i
Se a probabilidade de qi = 1 for 1/2, então este método requer ‘n’ subtrações e uma média de
‘n/2’ somas. Para o algoritmo abaixo, sejam ‘D’ os ‘n’ bits mais significativos do dividendo,
‘d’ os ‘n’ bits menos significativos do dividendo, ‘v’ o divisor (em ‘n’ bits). Para ‘d’ e ‘q’
utiliza-se a mesma variável.
1. Início: Se v for zero, terminar indicando erro de divisão por zero. Senão, deslocar
(D d) para a esquerda: (D d) ← desl.esquerda(D d 0). Se D for maior ou igual a v,
ocorrerá estouro no quociente. Neste caso, terminar indicando estouro. Senão,
fazer r ← D, i ← ‘n’ e q ← d.
2. Subtrair r ← r–v. Se r resultante for positivo, colocar 1 no bit menos significativo
de q. Senão, colocar zero no bit menos significativo de q e restaurar r através da
soma r ← r+v.
3. Decrementar i (i ← i–1). Se i=0, encerrar. O resto está em ‘r’ e o quociente em ‘q’.
4. Deslocar (r q) para esquerda: (r q) ← desl.esquerda(r q 0). Ir para o passo 2.

Na adaptação para o Ahmes, assume-se que o dividendo esteja nos endereços 128 (D - bits
mais significativos) e 129 (d - bits menos significativos), o divisor v no endereço 130, o
quociente q deve ser colocado em 131 e o resto r em 132. O estado da divisão é indicado na
posição 133 (-1 indica estouro na divisão, 0 indica divisão por zero e 1 indica divisão

6-14
normal). Para contador será utilizada a posição 134. Para as constantes, são utilizadas as
seguintes posições: 135 para zero, 136 para um, 137 para menos um (255) e 138 para oito.
Note-se que para d e q e para r e D são utilizadas as mesmas palavras de memória (131 e
132, respectivamente), o que pode ser visto nas instruções dos endereços 7 e 12.

Endereço Instrução Comentários


0 LDA 130 Carrega o divisor
2 JZ 60 Se for zero, termina
4 LDA 129 Carrega d em A
6 SHL Desloca para esquerda, bit mais significativo vai para carry
7 STA 131 Salva d como q (quociente temporário)
9 LDA 128 Carrega D em A
11 ROL Desloca para esquerda
12 STA 132 Salva D como r (resto temporário)
14 SUB 130 Calcula D – v
16 JP 64 Se D ≥ v, irá ocorrer estouro
18 LDA 138 Tudo bem ( D < v ). Divisão pode ser realizada
20 STA 134 Inicializa contador com ‘n’ (8 no caso)
22 LDA 132 Início do laço. Obtém r
24 SUB 130 Calcula r – v
26 JN 36 Se negativo, nada a fazer (não salva r)
28 STA 132 Se positivo, atualiza r
30 LDA 131 Carrega q
32 OR 136 Coloca bit do quociente em 1
34 STA 131 Salva q
36 LDA 134 Carrega contador
38 SUB 136 Decrementa contador de laço
40 STA 134 Salva contador
42 JZ 56 Se for zero, termina normalmente
44 LDA 131 Carrega q
46 SHL Desloca para esquerda, bit mais significativo vai para carry
47 STA 131 Salva q
49 LDA 132 Carrega r
51 ROL Desloca para esquerda
52 STA 132 Salva r
54 JMP 22 Volta para início do laço
56 LDA 136 Sinaliza fim normal
58 JMP 66
60 LDA 135 Sinaliza divisão por zero
62 JMP 66
64 LDA 137 Sinaliza estouro na divisão
66 STA 133 Armazena indicador de estado
68 HLT Fim
Note-se que o programa está otimizado no que se refere à restauração do ‘r’. A subtração é
realizada (posições 22 e 24) e testada (posição 26). No instante do teste, o resultado ‘r–v’
está no acumulador, e a variável ‘r’ na memória ainda não foi atualizada. Assim, a
“restauração de r” é realizada simplesmente não salvando o resultado do acumulador, e a
operação ‘r ← r–v’ é realizada salvando-se o acumulador (posição 28).

Uma técnica um pouco diversa da descrita acima pode ser empregada para eliminar a
necessidade de realizar a soma restauradora. Esta técnica é baseada no fato de uma
restauração da forma
ri ← r i + v
é seguida no próximo passo por uma subtração da forma:
ri-1 ← 2ri – v

6-15
Reunindo-se as duas equações tem-se
ri-1 ← 2(ri + v) – v = 2r i + 2v – v
ou seja,
ri-1 ← 2ri + v
Assim, quando qi = 1, que indica um valor positivo para ri, ri-1 é calculado por subtração do
divisor: ri-1 ← 2ri – v. Por outro lado, quando q i = 0, r i-1 é calculado por uma simples
soma: ri-1 ← 2ri + v. Assim, o cálculo de cada um dos bits do quociente requer uma soma
ou uma subtração, mas não ambas. Assim, este método, denominado de divisão sem
restauração, necessita de ‘n’ somas e subtrações, enquanto que o método da divisão com
restauração requer uma média de 3n/2 somas e subtrações.
Para o algoritmo abaixo, sejam ‘r’ os ‘n’ bits mais significativos do dividendo, ‘d’ os ‘n’
bits menos significativos do dividendo, ‘v’ o divisor (em ‘n’ bits). Para controlar se a
próxima operação será uma soma ou subtração, usa-se um elemento auxiliar ‘aux’,
inicializado para começar-se com uma subtração.
1. Início: Se v for zero, terminar indicando erro de divisão por zero. Senão, deslocar
(D d) para a esquerda: (D d) ← desl.esquerda(D d 0). Se D for maior ou igual a v,
ocorrerá estouro no quociente. Neste caso, terminar indicando estouro. Senão,
fazer r ← D, i ← ‘n’ e aux ← 1.
2. Se aux=1, calcular r ← r–v. Senão, calcular r ← r+v. Se o ‘r’ resultante for
positivo, então colocar 1 no bit menos significativo de q e fazer aux ← 1. Se o ‘r’
resultante for negativo, colocar 0 no bit menos significativo de q e fazer aux ← 0.
3. Decrementar i (i ← i–1). Se i=0, ir para 5.
4. Deslocar (r d) para esquerda: (r d) ← desl.esquerda(r d 0). Deslocar q para a
esquerda: q ← desl.esquerda(q 0). Ir para o passo 2.
5. Se r<0, então r ← r+v. Encerrar. O resto está em ‘r’ e o quociente em ‘q’

Como pode ser visto no passo 2, se o dividendo após uma subtração ficar negativo, ele não é
restaurado, mas sim no próximo passo soma-se o divisor (ao invés de subtrair). Isto é
controlado pelo “flag” aux. Assim, sempre que a operação anterior produzir um dividendo
positivo, a próxima operação é uma subtração; senão, se o dividendo for negativo, a próxima
operação é uma soma. Este método necessita, entretanto, corrigir o resto, como indicado no
passo 5.

Na adaptação para o Ahmes, assume-se que o dividendo esteja nos endereços 128 (D - bits
mais significativos) e 129 (d - bits menos significativos), o divisor v no endereço 130, o
quociente q deve ser colocado em 131 e o resto r em 132. O estado da divisão é indicado na
posição 133 (-1 indica estouro na divisão, 0 indica divisão por zero e 1 indica divisão
normal). Para contador será utilizada a posição 134. Para as constantes, são utilizadas as
seguintes posições: 135 para zero, 136 para um, 137 para menos um (255) e 138 para oito.
Para a variável ‘aux’ usa-se a posição 139.

Endereço Instrução Comentários


0 LDA 130 Carrega o divisor
2 JZ 92 Se for zero, termina
4 LDA 129 Carrega d em A
6 SHL Desloca para esquerda, bit mais significativo vai para carry
7 STA 131 Salva d como q (quociente temporário)
9 LDA 128 Carrega D em A
11 ROL Desloca para esquerda
12 STA 132 Salva D como r (resto temporário)
14 SUB 130 Calcula D – v

6-16
16 JP 96 Se D ≥ v, irá ocorrer estouro
18 LDA 138 Tudo bem ( D < v ). Divisão pode ser realizada
20 STA 134 Inicializa contador com ‘n’ (8 no caso)
22 LDA 136
24 STA 139 Inicializa aux com 1
26 LDA 139 Início do laço. Testa aux
28 JZ 38
30 LDA 132 Aux = 1
32 SUB 130 Calcula r – v
34 STA 132 Atualiza r
36 JMP 44
38 LDA 132 Aux = 0
40 ADD 130 Calcula r + v
42 STA 132 Atualiza r
44 JN 56 Testa o resultado da operação
46 LDA 131 Carrega q
48 OR 136 Coloca bit do quociente em 1
50 STA 131 Salva q
52 LDA 136 Faz aux=1 (próxima operação é uma subtração)
54 JMP 58
56 LDA 135 Faz aux=0 (próxima operação é uma soma)
58 STA 139 Atualiza variável aux
60 LDA 134 Carrega contador
62 SUB 136 Decrementa contador de laço
64 STA 134 Salva contador
66 JZ 80 Se for zero, termina normalmente
68 LDA 131 Carrega q
70 SHL Desloca para esquerda, bit mais significativo vai para carry
71 STA 131 Salva q
73 LDA 132 Carrega r
75 ROL Desloca para esquerda
76 STA 132 Salva r
78 JMP 26 Volta para início da laço
80 LDA 132 Fim do laço. Testa valor do resto
82 JP 88 Se for positivo, está correto
84 ADD 130 Se negativo, soma divisor ao resto
86 STA 132 Salva resto corrigido
88 LDA 136 Sinaliza fim normal
90 JMP 98
92 LDA 135 Sinaliza divisão por zero
94 JMP 98
96 LDA 137 Sinaliza estouro na divisão
98 STA 133 Armazena indicador de estado
100 HLT Fim

No lugar de corrigir o resto, alguns autores preferem realizar o laço ‘n–1’ vezes (e não ‘n’
vezes, como acima), e na última passagem forçar o bit menos significativo do quociente para
1. O resultado final sempre será algebricamente correto, no sentido que quociente vezes
divisor mais resto é igual ao dividendo, mas pode produzir “anomalias” como 21 ÷ 5 = 5 e
resta –4, ou então até mesmo 20 ÷ 5 = 5 e resta –5 (!). Mas 15 ÷ 5 = 3 e resta zero, assim
como 19 ÷ 5 = 3 e resta 4. Como o quociente sempre será impar (devido ao bit menos
significativo ser forçado para um), esta anomalia ocorre sempre que o quociente correto seria
um número par. A correção, entretanto, é fácil: sempre que o resto for negativo, basta
subtrair um do quociente e somar o divisor ao resto.

Exemplo de uso do método: seja a divisão de 000111000111 por 010001, ambos


interpretados como números em complemento de dois (note-se que ambos são positivos):

6-17
1. i ← 0, aux ← 1, r ← 000111, d ← 000111. Deslocando (r d) para esquerda,
resulta em r ← 001110, d ← 001110. Como r<v e v≠0, a divisão pode prosseguir.
2. r ← 001110 – 010001 = 001110+ 101111 = 111101, q ← 0, aux ← 0
3. i ← 1; i < 6; ir para 4
4. r ← 111010, d ← 011100, q ← 0x
2. r ← 111010 + 010001 = 001011, q ← 01, aux ← 1
3. i ← 2; i < 6; ir para 4
4. r ← 010110, d ← 111000, q ← 01x
2. r ← 010110 – 010001= 010110+ 101111 = 000101, q ← 011, aux ← 1
3. i ← 3; i < 6; ir para 4
4. r ← 001011, d ← 110000, q ← 011x
2. r ← 001011 – 010001= 001011+ 101111 = 111010, q ← 0110, aux ← 0
3. i ← 4; i < 6; ir para 4
4. r ← 110101, d ← 100000
2. r ← 110101 + 010001= 000110, q ← 01101, aux ← 1
3. i ← 5; i < 6; ir para 4
4. r ← 001101, d ← 000000, q ← 01101x
2. r ← 001101 – 010001= 001101+ 101111 = 111100, q ← 011010, aux ← 0
3. i ← 6; i ≥ 6; ir para 5
5. r ← 111100 + 010001 = 001101. Quociente = 011010 e resto = 001101. Ou seja,
dividindo-se 455 por 17 obtém-se 26 e resta 13.
Seja agora a divisão de 001011000010 por 010011, ambos em complemento de dois. Neste
caso, o passo um indica que r > v, ou seja, 010110 > 010011. Neste caso, a divisão não é
possível, pois o dividendo não é representável em seis bits (estouro de representação). Em
decimal, a divisão desejada seria de 706 por 19, o que resultaria em um quociente de 37, que
não é representável em complemento de dois em seis bits.
Na divisão de 001000010010 por 010011, o resultado obtido será 011011, com resto de
010001. Neste caso, o resto já é produzido correto, sem necessidade de correção no
passo 5.

6 . 5 Divisão binária (números em complemento de dois, positivos ou


negativos)
O método da divisão sem restauração pode ser generalizado para números inteiros positivos e
negativos. Para a divisão ser possível sem estouro, a comparação inicial requer que o valor
absoluto de ‘r’ seja menor que o valor absoluto de ‘v’. Se o sinais de dividendo e divisor
forem iguais, inicia-se com uma subtração; senão inicia-se com uma soma. Após cada
subtração (ou soma), comparam-se os sinais do divisor e do resultado. Se forem iguais, um
bit 1 é colocado no quociente, e a próxima operação é uma subtração. Se forem diferentes,
um bit 0 é adicionado ao quociente, e a próxima operação é uma soma. O bit final do
quociente é forçado para 1, independente do valor da análise dos sinais.

O resultado está algebricamente correto, no sentido do quociente ser igual ao divisor vezes o
quociente mais o resto. Entretanto, pode ocorrer que dividendo e resto tenha sinais
diferentes, assim como que o resto seja igual ao divisor. Para corrigir-se quociente e resto,
de forma que o resto seja sempre menor que o divisor e seu sinal igual ao do dividendo, o
seguinte procedimento é necessário:

6-18
• se o resto for zero, o resultado está correto.
• se o resto for diferente de zero, comparam-se os sinais do dividendo e do resto. Se
forem diferentes, então corrige-se o resultado:
• se dividendo e divisor tiverem os mesmos sinais, subtrai-se um do quociente o
soma-se o divisor ao resto para obter-se o resto real.
• se dividendo e divisor tiverem sinais diferentes, soma-se um ao quociente e
subtrai-se o divisor do resto para a obtenção do resto real.
• se o resto for diferente de zero, dividendo e resto forem ambos negativos, e o valor
absoluto do resto for igual ao valor absoluto do divisor, então o resto é zero e o
quociente deve ser corrigido. Se o divisor for negativo, soma-se um ao quociente, caso
contrário subtrai-se um.

6 . 6 Exercícios resolvidos

Atenção: para os exercícios a seguir todos os números sugeridos são inteiros positivos.
1. Multiplicar 1011 por 1101.
Empregando-se o algoritmo explicado na seção 4.1, tem-se o desenvolvimento apresentado a
seguir. As variáveis são: M = 1011 e m = 1101.
1. i ← 0, R ← 0, r ← 0, M ← 1011, m ← 1101.
2. m 0 = 1, continuar em 3.
3. R ← 0 + 1011 = 1011; c = 0
4. (Rr) ← desl.dir(010110000) = (01011000)
5. i ← 1; i < 4, ir para 2.
2. m 1 = 0, c = 0, continuar em 4.
4. (Rr) ← desl.dir(0010110000) = (00101100)
5. i ← 2; i < 4, ir para 2.
2. m 2 = 1, continuar em 3.
3. R ← 0010 + 1011 = 1101; c = 0
4. (Rr) ← desl.dir(011011100) = (01101110)
5. i ← 3; i < 4, ir para 2.
2. m 3 = 1, continuar em 3.
3. R ← 0110 + 1011 = 0001; c = 1
4. (Rr) ← desl.dir(100011110) = (10001111)
5. i ← 4; fim. Resultado = 10001111.

2. Multiplicar 11010 por 1111.


1. i ← 0, R ← 0, r ← 0, M ← 11010, m ← 01111.
2. m 0 = 1, continuar em 3.
3. R ← 0 + 11010 = 11010; c = 0
4. (Rr) ← desl.dir(01101000000) = (0110100000)
5. i ← 1; i < 5, ir para 2.
2. m 1 = 1, continuar em 3.
3. R ← 01101 + 11010 = 00111; c = 1
4. (Rr) ← desl.dir(10011100000) = (1001110000)
5. i ← 2; i < 5, ir para 2.
2. m 2 = 1, continuar em 3.
3. R ← 10011 + 11010 = 01101; c = 1

6-19
4. (Rr) ← desl.dir(10110110000) = (1011011000)
5. i ← 3; i < 5, ir para 2.
2. m 3 = 1, continuar em 3.
3. R ← 10110 + 11010 = 10000; c = 1
4. (Rr) ← desl.dir(11000011000) = (1100001100)
5. i ← 4; i < 5, ir para 2.
2. m 4 = 0, c = 0, continuar em 4.
4. (Rr) ← desl.dir(01100001100) = (0110000110)
5. i ← 5; fim. Resultado = 110000110.

3. Sugestões de valores para outros exercícios:


a) Multiplicar 11001 por 10111.
b) Multiplicar 1101 por 101.
c) Multiplicar 110000 por 110100.
d) Multiplicar 101 por 1000.
e) Multiplicar 110 por 111.

Respostas:
a) Produto = 1000111111
b) Produto = 1000001
c) Produto = 100111000000
d) Produto = 101000
e) Produto = 101010
4. Dividir 10010100 por 1011:
Tem-se que D = 10010100 e v = 1011. Então inicia-se a aplicação do algoritmo (seção 4.3):
1. r ← 10010, i ← 4
2. r ≥ v (10010 ≥ 1011). Então r ← 10010 – 1011 = 0111 e q ← 1
3. i ← 3
4. r ← 01111, q ← 1x
2. r ≥ v (01111 ≥ 1011). Então r ← 01111 – 1011 = 0100 e q ← 11
3. i ← 2
4. r ← 01000, q ← 11x
2. r < v (01000 < 1011). Então q ← 110
3. i ← 1
4. r ← 10000, q ← 110x
2. r ≥ v (10000 ≥ 1011). Então r ← 10000 – 1011 = 0101 e q ← 1101
3. i ← 0, encerrar. Quociente = 1101 e resto = 0101

5. Dividir 1100101001 por 1111:


Tem-se que D = 1100101001 e v = 11111. Então:
1. r ← 110010, i ← 5
2. r ≥ v (110010 ≥ 11111). Então r ← 110010 – 11111 = 10011 e q ← 1
3. i ← 4
4. r ← 100111, q ← 1x
2. r ≥ v (100111 ≥ 11111). Então r ← 100111 – 11111 = 1000 e q ← 11
3. i ← 3
4. r ← 010000, q ← 11x

6-20
2. r < v (010000 < 11111). Então q ← 110
3. i ← 2
4. r ← 100000, q ← 110x
2. r ≥ v (100000 ≥ 11111). Então r ← 100000 – 11111 = 00001 e q ← 1101
3. i ← 1
4. r ← 000011, q ← 1101x
2. r < v (000011 < 11111). Então q ← 11010
3. i ← 0, encerrar. Quociente = 11010 e resto = 11

6. Sugestões de valores para outros exercícios:


a) Dividir 1000111111 por 11001.
b) Dividir 1000101 por 1101.
c) Dividir 100111011000 por 110000.
d) Dividir 101010 por 101.
e) Dividir 101010 por 110.

Respostas:
a) Quociente = 10111 e resto = 0
b) Quociente = 101 e resto = 100
Obs.: Se você obteve resposta incorreta para este exercício, verifique quantos dígitos
usou para representar D (D deve ter 2n bits)
c) Quociente = 110100 e resto = 11000
d) A aplicação do algoritmo da seção 6.3 não é possível, uma vez que obtém-se resto
igual ao divisor no passo 2. Usando a divisão “convencional” obtém-se: quociente =
1000 e resto = 10 (ocorre estouro de representação no quociente).
e) Quociente = 111 e resto = 0

6-21
Capítulo
SETE
Números em Ponto Fixo e Ponto Flutuante

7 . 1 Números em ponto fixo

Observação inicial: os termos “ponto fixo” e “ponto flutuante” são traduções diretas dos
termos ingleses “fixed point” e “floating point”, que se referem ao símbolo utilizado nos
países de língua inglesa para representar a vírgula decimal. As traduções corretas seriam,
respectivamente, “vírgula fixa” e “vírgula flutuante”. Entretanto, devido ao fato dos termos
“ponto fixo” e “ponto flutuante” já serem de uso corrente na literatura especializada nacional,
eles serão empregados aqui também .
Todos os números tratados até agora foram números inteiros, onde uma vírgula binária não
apareceu explicitamente. Entretanto, estes números podem ser interpretados como
apresentando uma vírgula à direita do dígito menos significativo (a posição das unidades).
Assim, por exemplo, o número binário em complemento de dois
01100111
que corresponde ao número decimal 103, pode ser interpretado como apresentando uma
vírgula à extrema direita
0 1 1 0 0 1 1 1,
sem que isto altere o valor por ele representado. Tem-se então uma representação em “ponto
fixo”, mas sem parte fracionária. Caso deseje-se representar frações, deve-se reservar um
certo número de bits para isto. Naturalmente, a quantidade de bits utilizáveis para a parte
inteira diminui correspondentemente. Por exemplo, para a mesma cadeia de bits acima, tem-
se os seguintes números, conforme a posição da vírgula:
0 1 1 0 0 1 1,1 Decimal = 51,5 (1 bit para fração)
0 1 1 0 0 1,1 1 Decimal = 25,75 (2 bits para fração)

0 1 1 0 0,1 1 1 Decimal = 12,875 (3 bits para fração)

0 1 1,0 0 1 1 1 Decimal = 3,21875 (5 bits para fração)

0,1 1 0 0 1 1 1 Decimal = 0,8046875 (7 bits para fração)

,01 1 0 0 1 1 1 Decimal = 0,40234375 (8 bits para fração)


Ao contrário dos exemplos acima, a vírgula não é representada explicitamente. Para uma
determinada notação em ponto fixo, indica-se somente quantos bits são usados para a fração
e quantos bits representam a parte inteira. Todos os números manipulados seguem então a
mesma notação. Dos ‘n’ bits utilizados para representar os números, utilizam-se então ‘t’ bits
(t≥0) para a parte inteira e ‘f’ bits (f≥0) para a parte fracionária, com a restrição de que
t+f=n. Observe-se ainda que:

7-1
• a quantidade total de valores representáveis permanece a mesma (2n), independente da
posição da vírgula (isto é, independente dos valores de ‘t’e ‘f’).
• a faixa de valores representáveis depende da posição da vírgula (e da convenção
utilizada, naturalmente). De um modo geral, a faixa é dada pelas mesmas fórmulas
vistas anteriormente (sinal/magnitude, complemento de dois, etc), mas todos os
números são agora divididos por um fator igual a 2f. Assim, por exemplo, em
complemento de dois, o maior número negativo é dado por –2n-1/2f (ou seja, –2n-f-1);
o maior número positivo é calculado por (2n-1–1)/2f.

• os números fracionários não são contínuos, mas sim estão separados entre si por uma
diferença igual a 2-f.

A tabela a seguir ilustra estas observações para números de 8 bits, representados em


complemento de dois (a coluna intervalo indica a diferença entre dois números consecutivos):

n t f Quant. num. Menor num. Maior num. Intervalo


8 8 0 256 -128 127 1
8 7 1 256 -64 63,5 0,5
8 6 2 256 -32 31,75 0,25
8 5 3 256 -16 15,875 0,125
8 4 4 256 -8 7,9375 0,0625
8 3 5 256 -4 3,96875 0,03125
8 2 6 256 -2 1,984375 0,015625
8 1 7 256 -1 0,9921875 0,0078125
8 0 8 256 -0,5 0,49609375 0,00390625
Tabela 7.1 - Números em ponto fixo de 8 bits

7 . 2 Soma e subtração em ponto fixo

As operações de soma e subtração em ponto fixo se realizam exatamente da mesma maneira


que para números inteiros. Naturalmente, somente podem ser somados (ou subtraídos)
números que possuam a mesma posição para a vírgula. Ou seja, ‘t’ e ‘f’ devem ser iguais
para todos os operandos.

Entretanto, números em ponto fixo com diferentes posições para a vírgula também podem
ser operados, desde que um dos números seja convertido para a representação do outro.
Sejam os números ‘m1’ e ‘m2’, com comprimentos ‘t1’ e ‘f1’, e ‘t2’ e ‘f2’, respectivamente.
O resultado deve ter a representação de ‘m1’, ou seja, comprimentos ‘t1’ e ‘f1’ para as partes
inteira e fracionária.

Se ‘t1’ > ‘t2’ (a parte inteira de m1 utiliza mais bits que a parte inteira de m2), então valem as
regras:

1. A parte inteira de m2, com t2 bits, deve ser extendida para t1 bits. Os bits devem ser
tais que o sinal e o valor do número sejam mantidos. Assim, por exemplo, para
números inteiros positivos, t1 – t 2 bits em zero são acrescentados à esquerda de m2.
Para números em complemento de dois, o sinal deve ser duplicado para a esquerda por
t1 – t2 bits.

2. A parte fracionária de m2 deve ser reduzida para f1 bits. Isto pode ocorrer de duas
maneiras: por truncagem ou por arredondamento. Na truncagem, os f2–f1 bits a direita
de m2 são simplesmente eliminados. No arredondamento, primeiro soma-se 2–(f1+1) à

7-2
m2 e depois realiza-se a truncagem. Note-se que isto equivale a somar um ‘1’ na
posição f2 – f 1 do número; em termos aritméticos, significa que se a fração a ser
eliminada é maior ou igual a 0,5 arredonda-se para cima, senão arredonda-se para
baixo.

A tabela abaixo ilustra alguns exemplos de conversão. Considere-se que o número final deve
ser representado com 4 bits de parte inteira e 4 bits de parte fracionária. ‘T’ representa a nova
fração sob truncagem, e ‘A’ representa a nova fração sob arredondamento.

Num.original Representação Parte inteira Fração (T) Fração (A)


01,101101 Int. positivo 0001 1011 1011
11,101101 Int. positivo 0011 1011 1011
010,00111 Int. positivo 0010 0011 0100
01,101101 Compl.dois 0001 1011 1011
11,101101 Compl.dois 1111 1011 1011
010,00111 Compl.dois 0010 0011 0100
Tabela 7.2 - Exemplos de redução de fração

Se ‘t1’ < ‘t2’ (a parte inteira de m1 utiliza menos bits que a parte inteira de m 2), então valem
as regras:

1. A parte inteira de m2, com t2 bits, deve ser reduzida para t1 bits. Isto só é possível se
esta parte inteira puder ser representada com somente t1 bits. Se isto não for possível,
a operação não pode ser realizada, pois ocorre estouro de representação. Para esta
redução valem as regras analisadas na multiplicação, quando se reduzia um número de
‘2n’ bits para ‘n’ bits.
2. A parte fracionária de m2 deve ser ampliada para f1 bits. Para isto, simplesmente
acrescenta-se f1 – f2 bits em zero à direita da fração de m1.

A tabela a seguir ilustra alguns exemplos de conversão. Considere-se que o número final
deve ser representado com 4 bits de parte inteira e 4 bits de parte fracionária.

Num.original Representação Parte inteira Fração


01101,101 Int. positivo 1101 1010
111011,01 Int. positivo Estouro 0100
01000,111 Int. positivo 1000 1110
0110110,1 Compl.dois Estouro 1000
11101,101 Compl.dois 1101 1010
0001011,1 Compl.dois Estouro 1000
Tabela 7.3 - Exemplos de redução de mantissa

Observe-se que sempre é possível converter um número em ponto fixo para uma
representação que tenha menos bits na parte fracionária (ou mais bits na parte inteira),
enquanto que o contrário nem sempre é possível Assim, quando se deve operar dois
números em ponto fixo com t1 e t2 diferentes, costuma-se normalizar os operandos para
aquela representação com maior parte inteira.

7 . 3 Multiplicação em ponto fixo

A operação de multiplicação em ponto fixo se realiza exatamente da mesma maneira que a


multiplicação de números inteiros (com ou sem sinal). Deve-se somente observar a nova
posição da vírgula: se os dois operandos (de ‘n’ bits cada) tiverem ‘t’ bits de parte inteira e
‘f’ bits de parte fracionária, o resultado apresentará ‘2n’ bits, com ‘2t’ bits de parte inteira e
‘2f’ bits de parte fracionária.

7-3
De um modo mais geral, se o multiplicando apresentar t1 e f 1 bits para as partes inteira e
fracionária, e se o multiplicador tiver t 2 e f 2 bits, então o resultado terá t1+t2 bits na parte
inteira e f1+f2 bits na parte fracionária. Como na multiplicação inteira, nunca ocorre estouro
de representação se forem considerados todos os bits do resultado.

Após a multiplicação, se o resultado deve ser reduzido para ‘n’ bits (t + f), isto deve ser feito
em duas etapas:
1. A redução da parte inteira para ‘t’ bits. Para isto elimina-se os bits mais significativos,
da mesma maneira que o realizado para a multiplicação inteira. Nesta redução pode
ocorrer estouro.
2. A redução da parte fracionária para ‘f’ bits. Para isto eliminam-se os bits menos
significativos, por truncagem ou por arredondamento, tal como foi explicado na soma
de números em ponto fixo.

7 . 4 Divisão em ponto fixo


A operação de divisão em ponto fixo é realizada exatamente da mesma maneira que a divisão
de números inteiros, observando-se as posições das vírgulas do dividendo e do divisor. Os
algoritmos de divisão inteira necessitam de ‘2n’ bits para o dividendo e ‘n’ bits para o
divisor. Se o divisor tem ‘t’ bits inteiros e ‘f’ bits de fração, então o dividendo deve
apresentar ‘2t’ bits na parte inteira e ‘2f’ bits na parte fracionária.
1. Para estender a parte inteira, duplicam-se os bits mais significativos de acordo com a
representação (zeros para números positivos, ou com o bit de sinal para números em
complemento de 2).
2. Para estender a parte fracionária, basta acrescentar zeros à direita.
Desta maneira obtém-se, após a execução da divisão, um quociente em ‘n’ bits (t+f) e um
resto, também em ‘n’ bits (mas com ‘2f’ bits de parte fracionária!).

7 . 5 Números em ponto flutuante


A faixa de números que podem ser representados em ponto fixo é insuficiente para a maioria
das aplicações científicas, onde existe a necessidade de representar-se números muito
grandes e/ou números muito pequenos. Para contornar este problema, desenvolveu-se a
notação científica, onde um quintilhão é representado por 1,0 x 1018, em vez de escrevê-lo
por extenso (1 000 000 000 000 000 000). A representação de números em ponto flutuante é
basicamente a versão binária da notação científica.

A cada número em ponto flutuante estão associados na realidade três outros números: a
mantissa ‘m’, o expoente ‘e’ e a base ‘b’. No caso dos computadores atuais, a base utilizada
é a binária, ou seja, b=2. O número em ponto flutuante é então calculado por:
N = m x be
Como a base é uma constante para um determinado sistema, o número em ponto flutuante é
então representado por um par (m,e), onde ‘m’ é uma fração ou um inteiro, e ‘e’ é o exponte
(sempre um inteiro). Note-se que ambos, mantissa e expoente, podem ser positivos ou
negativos.

A precisão de um número em ponto flutuante é determinada primariamente pelo número de


bits utilizados pela mantissa. A faixa de representação R depende do número de bits do
expoente. Apesar dos números em ponto flutuante representarem números reais, a faixa R
não é contínua. Somente uma quantidade limitada de números (2n, onde n é a quantidade

7-4
total de bits da mantissa e do expoente) pode ser representada. Os números estão então
dispersos dentro desta faixa.

Os números em ponto flutuante são inerentemente redundantes, no sentido que um mesmo


número pode ser representado de mais de uma maneira. Por exemplo, um quintilhão pode
ser representado por 1,0.1018, ou 0,1.10 19, ou mesmo 100.1016. Assim, é desejável que
exista uma forma normalizada de representar um número. Para tanto, utiliza-se somente
“mantissas normalizadas”. Uma mantissa está normalizada quando é constituída somente de
uma parte fracionária (não existe parte inteira) e quando o primeiro dígito a direita da vírgula
é diferente de zero. Assim, a forma normalizada de representar um quintilhão é dada por
0,1.1019.

Na base binária, a normalização da mantissa exige que os seus dois bits mais significativos
sejam diferentes. Assim, para números positivos a mantissa inicia sempre por 0,12. Para
números em complemento de dois, isto implica em que o dígito mais significativo da
mantissa e o bit de sinal sejam diferentes - para números positivos a mantissa inicia então por
0,1 2 e para números negativos a mantissa inicia por 1,02. Assim, a normalização restringe a
magnitude |M| de uma mantissa ao intervalo
1/2 ≤ |M| < 1
Um número não normalizado é normalizado facilmente, através de deslocamentos da
mantissa para a direita ou esquerda e incrementos ou decrementos do expoente,
respectivamente. Note-se que um eventual estouro de representação na mantissa é facilmente
resolvido: basta deslocar a mantissa para a direita, corrigir o bit mais significativo, e somar
um ao expoente. Um estouro de representação no expoente, entretanto, indica um estouro na
capacidade de representação dos números em ponto flutuante. Se o expoente ultrapassou o
maior expoente positivo, fala-se em “overflow”; se o expoente ultrapassou o menor
negativo, fala-se então em “underflow”, e o resultado fornecido normalmente é zero.
A representação do número zero apresenta algumas características peculiares, que
influenciam fortemente no formato utilizado para representar números em ponto flutuante em
binário. A mantissa deve ser naturalmente, igual a zero, mas o expoente pode apresentar
qualquer valor, uma vez que 0.be é igual a 0 para todos os valores de ‘b’ e ‘e’. Com isto
tem-se:
1. A mantissa do número zero deve apresentar todos os seus bits em zero, para facilitar o
teste por zero. Note-se que isto vai contra a definição formal de uma mantissa
normalizada.
2. O número zero é na realidade a menor quantidade absoluta possível de representação.
Durante a realização de diversos cálculos, entretanto, arredondamentos e truncagens
podem levar a resultados que sejam números bem pequenos, mas não exatamente
iguais a zero. Para indicar o fato do zero estar bem próximo em magnitude destes
números, o expoente escolhido para o zero deve ser o maior número negativo repre-
sentável. Assim, por exemplo, se o expoente possuir ‘k’ bits e for representado em
complemento de dois, o expoente utilizado para o zero deverá ser –2k-1.
3. Pelas considerações anteriores, tem-se que o zero é representado por 0.b –2k-1 .
Entretanto, para facilitar o teste de um número para verificar se ele é igual a zero, é
desejável que o número zero seja uma sequência de bits em zero, sem nenhum bit em
um. Isto está em contradição com a representação acima. Para resolver este problema,
codifica-se o expoente “em excesso de 2 k-1”, ou seja, um expoente em zero significa
na realidade a maior magnitude negativa representável. A codificação em excesso
indica que existe uma quantidade a mais somada ao expoente (no caso, 2 k-1); para
obter-se o valor real do expoente deve-se subtrair esta quantidade do valor armazenado
no campo de expoente. Ou seja, e real = e – 2 k-1. Para um expoente de 8 bits em
complemento de dois, por exemplo, tem-se então uma codificação em “excesso de
128”: o expoente –128 é representado por 0; o expoente 0 é representado por 128; e o
expoente 127 (o maior possível) é dado por 255.

7-5
7 . 6 Formatos de números em ponto flutuante
Um número em ponto flutuante, representado em binário, deve fornecer as informações
relativas à mantissa (seu sinal e sua magnitude) e ao expoente (também seu sinal e sua
magnitude). Diversas representações podem ser utilizadas para isto (sinal/magnitude,
complemento de dois, etc). Devido a isto, existem diversos formatos adotados para
representar os números em ponto flutuante. Muitos deles são específicos para uma
determinada família de computadores ou para um determinado fabricante.

Por exemplo, o formato da série S/360-370 da IBM segue o formato comentado abaixo:

• o número utiliza 32 bits: 1 bit para o sinal da mantissa, 7 bits para o expoente e 24 bits
para o valor da mantissa.
• a mantissa é representada em sinal magnitude; o expoente é representado em excesso-
de-64.
• o bit mais significativo representa o sinal da mantissa; os sete bits seguintes
representam o expoente; e os 24 bits menos significativos representam o valor da
mantissa.
• a base utilizada é a base 16 (hexadecimal).

Assim, o número 0,125 x 16 5 tem a seguinte representação:


0 1000101 001000000000000000000000
E o número – 0,125 x 16-5 tem a representação:
1 0111011 001000000000000000000000
Note-se que, como a base é 16, a mantissa é normalizada para dígitos hexadecimais (e não
para dígitos binários).

O formato recomendado pela IEEE (Institute of Electrical and Electronics Engineers) é


descrito a seguir (existem três formatos: simples, de 32 bits, duplo, de 64 bits e quádruplo,
de 128 bits):

Simples Duplo Quádruplo


Campos:
S = sinal 1 bit 1 bit 1 bit
E = expoente 8 bits 11 bits 15 bits
L = primeiro bit (não representado) (não representado) 1 bit
F = fração 23 bits 52 bits 111 bits

Expoente
Excesso-de 127 1023 16383
Maior valor 255 2047 32767
Menor valor 0 0 0
Tabela 7.4 - Formato IEEE

Nesta notação, cinco grupos diferentes de números podem ser representados: números
normalizados, zero, números não-normalizados, infinito e não-números (NaN):

1. Os números normalizados utilizam um expoente que vai de 1 a 254 (ou 1 a 2046, ou 1


a 32766), o primeiro bit da mantissa (L) é sempre zero e por isto não é representado. O
valor do número é calculado por
(–1)s . 2 E-excesso . (L.F)
onde L.F representa a mantissa propriamente dita; L é o primeiro bit e F é a fração.
2. O Zero é representado por um número todo em zero (E=F=L=0); note-se que o zero
neste caso pode ter sinal.

7-6
3. Números não normalizados possuem o expoente em zero (E=0) e uma fração não zero.
Seu uso é restrito para representação do números que não podem ser normalizados
sem causar “underflow”.
4. O Infinito é representado pelo maior valor do expoente (E=255 ou 2047 ou 32767) e
por uma fração em zero (F=L=0). Note-se que o infinito pode ter sinal.
5. Não-números (Not a Number) são representados pelo maior expoente e por uma fração
diferente de zero. Seu uso previsto inclui a indicação de códigos de erro, situações
imprevistas, etc.

O bit de sinal (S) é representado no bit mais significativo; os bits seguintes representam o
expoente. Os bits menos significativos são destinados à mantissa. Note-se que o bit L não é
representado (exceto na notação quádrupla).

7 . 7 Soma e subtração de números em ponto flutuante


Números em ponto flutuante, para poderem ser somados ou subtraídos, devem apresentar o
mesmo expoente. Neste caso, a soma ou subtração é realizada sobre as mantissas. O número
resultado é formado com a mantissa resultado e o expoente dos operandos.

Como os expoentes devem ser iguais antes de poder-se realizar a operação propriamente dita,
expoentes diferentes devem ser igualados. O menor dos expoentes deve ser tornado igual ao
maior, e a mantissa correspondente a este expoente deve ser convenientemente deslocada
para a direita, de forma que o número representado pelo par (mantissa, expoente) não se
altere.

Sejam X e Y dois números em ponto flutuante; sejam Xm e Ym suas mantissas e Xe e Ye os


seus expoentes. As seguintes regras valem então para a soma (ou subtração):

1. Se Xe=Ye, então X±Y=(X m±Ym) . 2Xe

2. Se Xe<Ye, então X±Y=(X m.2 (Xe-Ye)±Ym) . 2Ye

3. Se Xe>Ye, então X±Y=(X m±Ym.2 (Ye-Xe)) . 2Xe

Por exemplo, no caso 2, a mantissa Xm associada ao menor expoente Xe deve ser deslocada
para a direita Ye–Xe vezes, para formar uma nova mantissa X m.2 (Xe-Ye). Esta nova
mantissa, que corresponde agora a um expoente Ye, pode ser operada com Ym. Note-se que,
quando a mantissa Xm é deslocada para a direita, existe uma perda de precisão nos seus bits
menos significativos, que são eliminados. E se a diferença Ye-Xe for maior que o número de
bits utilizados para representar a mantissa, nem é necessário realizar-se a soma (ou
subtração): todos os bits significativos de Xm seriam eliminados pelo deslocamento para a
direita (isto equivale a somar dois números, onde um deles é muito menor que o outro).

Como os números em ponto flutuante são normalmente armazenados na forma normalizada,


após a operação a mantissa resultado deve ser normalizada, através de deslocamentos para a
esquerda (direita) e decrementos (incrementos) do expoente, tal como explicado na seção 5.
Note-se que um eventual estouro de representação é resolvido através de um deslocamento
para a direita (exceto no caso em que o expoente já é o maior possível, caso em que
realmente ocorre estouro).

7 . 8 Multiplicação de números em ponto flutuante

Números em ponto flutuante são multiplicados facilmente. Basta multiplicar as mantissas e


somar os expoentes. A multiplicação segue essencialmente as mesmas regras da
multiplicação inteira; inclusive a mantissa resultado tem o dobro de bits de comprimento. Ela

7-7
é entretanto facilmente reduzida para o número normal de bits, através da eliminação dos
seus bits menos significativos (por truncagem ou arredondamento).
X x Y = (Xm x Ym) . 2(Xe+Ye)
Após a multiplicação o resultado também deve ser normalizado; durante este processo pode
ocorrer estouro de representação (se ocorrer estouro na representação do expoente).

7 . 9 Divisão de números em ponto flutuante

Números em ponto flutuante são divididos facilmente. Basta dividir as mantissas e subtrair
os expoentes. A mantissa do dividendo é inicialmente expandida para o dobro do seu número
de bits (pela inclusão de zeros a direita) e depois as duas mantissas são divididas pelo mesmo
algoritmo utilizado para números inteiros. Note-se que agora o resto é normalmente
desprezado, e as etapas que na divisão inteira corrigiam quociente e resto podem ser
eliminadas.
X ÷ Y = (X m ÷ Ym) . 2(Xe–Ye)
Após a divisão o resultado também deve ser normalizado; durante este processo pode ocorrer
estouro de representação (se ocorrer estouro na representação do expoente).

7-8
Capítulo
OITO
Codificações BCD, Numérica e Alfanumérica

8 . 1 Números e Aritmética BCD

Os computadores digitais atuais trabalham com números binários, uma vez que isto facilita
enormemente a construção destes computadores. No dia-a-dia, entretanto, o sistema decimal
é largamente utilizado (ainda!). Isto obriga a existência de rotinas de conversão de números
decimais para binário e vice-versa; estas rotinas existem em todos os computadores atuais.
Nos computadores mais antigos, entretanto, procurou-se desenvolver uma aritmética binária
que operasse com dígitos decimais, de forma a facilitar estas rotinas de conversão. Neste
sistema, em desuso nos dias de hoje, um grupo codificado de quatro bits é utilizado para
representar cada um dos dez dígitos decimais. Esta é a base do sistema BCD (Binary Coded
Decimal – Decimal Codificado em Binário).

Com quatro bits podem ser codificadas 16 combinações distintas (de 0000 a 1111), mas
existem somente 10 dígitos binários (de 0 até 9). Com isto tem-se 16!/6! = 2,9x10 10
diferentes possibilidades para implementar-se uma codificação BCD. Algumas das
combinações mais usuais são ilustradas na tabela apresentada a seguir. A segunda coluna
utiliza o NBCD (Natural BCD), visto que os dígitos são codificados de acordo com sua
representação em binário (com pesos 8,4, 2 e 1 para cada bit). O código de Aiken usa os
pesos 2, 4, 2 e 1 para cada bit, enquanto que o código de Stibitz (também chamado de
excesso de 3) usa os mesmos pesos do NBCD, mas soma três a cada representação. O
código 7421 usa os pesos 7, 4, 2 e 1, enquanto que o código 642-1 utiliza os pesos 6, 4, 2 e
–1.

Dígito decimal Cód. NBCD Cód.Aiken Cód.Stibitz Cód.7421 Cód. 642-1


(8421) (2421) (8421 – 3) (7421) (642-1)
0 0000 0000 0011 0000 0000
1 0001 0001 0100 0001 0011
2 0010 0010 0101 0010 0010
3 0011 0011 0110 0011 0101
4 0100 0100 0111 0100 0100
5 0101 1011 1000 0101 0111
6 0110 1100 1001 0110 1000
7 0111 1101 1010 0111 1011
8 1000 1110 1011 1001 1010
9 1001 1111 1100 1010 1101
Tabela 8.1 - Codificações BCD

Por sua proximidade do sistema binário, a codificação NBCD é a mais utilizada, e é


denominada por muitos autores simplesmente por BCD, e a aritmética com este código é
denominada de aritmética BCD. Nesta aritmética, os dígitos decimais são operados em
binário, agrupados de quatro em quatro. Quando se soma dois dígitos BCD em binário, o
dígito resultado pode estar em um de três casos:

1. Dígito legal (entre 0 e 9), sem “vai-um”. Neste caso o resultado está correto, e não
existe “vai-um” para o dígito seguinte.

8-1
2. Dígito ilegal sem “vai-um”. Neste caso o resultado está entre 10 e 15 (em binário); para
obter-se o dígito correto deve-se subtrair 10 do dígito (ou somar seis, o que é
equivalente), e gerar-se um “vai-um” para o dígito decimal seguinte.

3. Dígito legal com “vai-um”. Este caso ocorre quando o resultado cai entre 16 e 19; da
mesma maneira que o caso 2, para obter-se o dígito correto deve-se subtrair 10 do
dígito (ou somar seis). O “vai-um” gerado está correto.

Por exemplo, seja A=0832 e B=0983. Então tem-se:

A= 0000 1000 0011 0010


B= 0000 1001 1000 0011
0001 0001 1011 0101
caso 1 caso 3 caso 2 caso 1

Para corrigir o resultado, deve-se somar seis nos dígitos dos casos 2 e 3. No caso 2, ainda é
necessário propagar um “vai-um” (isto não é necessário no caso 3, pois o “vai-um” já foi
gerado).
1
0001 0001 1011 0101
0110 0110
0001 1000 0001 0101

Com isto tem-se 1815, que é o resultado correto. Uma desvantagem da correção do resultado
dessa maneira é que as correções do tratamento do “vai-um” no caso 2 podem criar novas
correções, como por exemplo no caso de A=0372 e B=0633:

A= 0000 0011 0111 0010


B= 0000 0110 0011 0011
0001 1001 1010 0101
caso 2
Com a correção do caso 2, tem-se:
1
0000 1001 1010 0101
0110
0000 1010 0000 0101
caso 2

E a nova correção deste novo caso 2 fornece o resultado correto (1005). Para evitar este
tratamento especial, foi criado o algoritmo de Hellerman, que soma 6 em todos os dígitos de
um dos operandos antes da soma das duas parcelas. Assim, só existem dois casos a serem
tratados, distinguidos pelo “vai-um”:
1. O resultado não deu “vai-um” e então caiu entre 6 e 15. Deve-se subtrair 6 para obter o
dígito correto.
2. O resultado produziu um “vai-um”. Então este “vai-um” já foi propagado e o dígito
está correto entre 0 e 9.
Por exemplo, seja A=0372 e B=0633. Então tem-se:

A= 0000 0011 0111 0010


soma de 6 0110 0110 0110 0110
0110 1001 1101 1000

Se a primeira etapa produzir um “vai-um” entre dígitos decimais, isto significa que o dígito
original do operando terá sido ilegal; este número está mal representado. A segunda etapa
realiza a soma de (A+6) com o segundo operando (B):

8-2
1 1
A+6= 0110 1001 1101 1000
B= 0000 0110 0011 0011
0111 0000 0000 1011
caso 1 caso 2 caso 2 caso 1
Na terceira e última etapa, ao invés de subtrair 6 (0110), soma-se 10 (1010) e ignora-se o
“vai-um”).
0111 0000 0000 1011
1010 1010
0001 0000 0000 0101

O resultado, 1005, já está correto.

Para a subtração utilizam-se técnicas semelhantes. Para maiores detalhes sobre aritmética
BCD pode-se consultar o livro “Projeto de Computadores Digitais”, de Glen George
Langdon Jr. e Edson Fregni, capítulo 2 (Códigos, números e aritmética). As operações de
divisão e multiplicação operam de forma semelhante aos métodos utilizados na nossa
aritmética decimal, mas atuando sobre cada dígito BCD individualmente. Estas operações
não possuem hoje em dia nenhum interesse prático e, devido às extensões dos seus
algoritmos, não serão abordadas aqui.

8 . 2 Codificação
Os computadores atuais trabalham unicamente com dígitos binários (bits); nenhum outro
símbolo é utilizado. Qualquer informação, seja ela numérica ou alfabética, deve ser
representada utilizando-se combinações adequadas destes bits. A este processo genérico de
representação dá-se o nome de codificação, ou seja, um mapeamento entre os símbolos
utilizados externamente ao computador e os grupos de bits escolhidos para representar estes
símbolos.
Todas as representações de números vistas até o momento (inteiros positivos,
sinal/magnitude, complemento de um, complemento de dois, ponto fixo, ponto flutuante,
BCD) são exemplos de codificações. O objetivo comum a todas estas codificações era
representar números no sistema de numeração arábico; para cada codificação foram
estudadas a maneira de representar os números no sistema binário e inclusive a maneira de
operar com estes números codificados.

Diversas outras aplicações, entretanto, também utilizam códigos. Assim, tem-se códigos
desenvolvidos para manipular caracteres alfanuméricos, símbolos especiais (pontuação,
letras gregas, etc), símbolos matemáticos, e toda a gama de informação que deve ser
manipulada ou armazenada em um computador.

De uma forma geral, se uma determinada codificação utiliza ‘n’ bits, podem ser
representados 2n símbolos distintos, ou seja, tem-se 2n combinações distintas que podem ser
realizadas com estes ‘n’ bits. Os códigos não necessitam utilizar todas as combinações
possíveis (como é o caso dos códigos BCD), nem a informação necessita estar codificada de
forma inequívoca (como é o caso do duplo zero em sinal/magnitude ou de números não
normalizados em ponto flutuante). Cada sistema de codificação pode estabelecer suas
próprias regras de formação e manipulação dos grupos de bits.

8 . 3 Códigos BCD (ou códigos de 4 bits ponderados)

Conforme já foi visto, nos códigos BCD um grupo codificado de quatro bits é utilizado para
representar cada um dos dez dígitos decimais. Como uma metodologia para realizar

8-3
operações aritméticas estes códigos estão em desuso, mas como uma maneira de armazenar
números decimais eles ainda são utilizados.

Como já foi visto, existem 2,9x10 10 diferentes possibilidades para implementar-se uma
codificação BCD. Algumas delas foram analisadas no capítulo anterior. Dois dos códigos
BCD ainda são bastante utilizados hoje em dia: o BCD natural e o código em excesso-de-três
(Stibitz). Ambos estão reproduzidos abaixo:

Dígito decimal NBCD Excesso-de-3


(8421) (8421 – 3)
0 0000 0011
1 0001 0100
2 0010 0101
3 0011 0110
4 0100 0111
5 0101 1000
6 0110 1001
7 0111 1010
8 1000 1011
9 1001 1100
Tabela 8.2 - Códigos BCD de 4 bits

O código BCD natural tem uma correspondência “natural” entre os dígitos decimais e a
codificação binária. Os próprios pesos das posições binárias seguem os valores utilizados no
sistema arábico (8, 4, 2 e 1). O código em excesso de três também tem os mesmos pesos,
mas a cada codificação binária soma-se 3.
Para o código NBCD (denominado simplesmente de BCD) foi desenvolvida uma aritmética
binária, conforme já foi visto anteriormente.
O mesmo ocorre com o código de excesso-de-três, que possui duas vantagens: nenhum
código utiliza a combinação 0000, e o código é auto-complementado para 9, ou seja, para se
obter o complemento de 9 de um código decimal, basta inverter todos os bits.

A aritmética em excesso de 3 é relativamente fácil. Quando dois dígitos em excesso de 3 são


somados, o resultado será em excesso de 6, e tem-se dois casos a tratar:
• A soma dos dois dígitos é nove ou menos. Neste caso, a soma em excesso resultará
em um dígito que é 15 ou menos, e nenhum carry será gerado. Nestes casos, basta
subtrair 3 do resultado para obter o dígito corretamente codificado em excesso de 3.

• A soma dos dois dígitos é 10 ou mais. Neste caso, a representação em excesso será
16 ou mais. Como X e X+16 tem o mesmo código, a diferença está na geração de
um ‘vai-um’. Para corrigir o resultado, ou seja, representar X em excesso de 3,
basta somar 3.

Assim, a regra para a soma em excesso de três é simples: soma-se os dígitos usando
aritmética binária; se um ‘vai-um’ é gerado, somar 3 (0011) ao dígito decimal; senão,
subtrair 3 (0011) ao dígito decimal (ou somar 1101 e desprezar o ‘vai-um’).

Diversos outros códigos também podem ser desenvolvidos atribuindo-se pesos distintos às
diversas posições; o nome destes códigos é normalmente formado pelos pesos. Abaixo são
exemplificados alguns deles (4221, 2421 (Aiken), 5421, 7421, e 642(-1), com peso
negativo). Note-se que a escolha dos pesos não determina de forma inequívoca a
codificação, mas apenas facilita sua compreensão. Assim, por exemplo, no código 4221 o
dígito decimal 2 pode ser codificado tanto por 0010 (como na tabela) como por 0100; o
dígito 6 poderia ser codificado como 1010 ou por 1100 (esta última utilizada na tabela).

8-4
Dígito decimal 4221 2421 5421 7421 642(-1)
0 0000 0000 0000 0000 0000
1 0001 0001 0001 0001 0011
2 0010 0010 0010 0010 0010
3 0011 0011 0011 0011 0101
4 1000 0100 0100 0100 0100
5 0111 1011 1000 0101 0111
6 1100 1100 1001 0110 1000
7 1101 1101 1010 0111 1011
8 1110 1110 1011 1001 1010
9 1111 1111 1100 1010 1101
Tabela 8.3 - Códigos BCD pouco utilizados

Cada um destes códigos sempre é desenvolvido com algum propósito ou aplicação específica
em mente. Por exemplo, o código 4221 é auto-complementado em 9 (como o são todos os
códigos cuja soma dos pesos é 9). Nenhum dos códigos da tabela anterior, entretanto, é
apropriado para operações aritméticas.

Outros códigos possíveis seriam 5221, 5321, 6331, 5211, 6321, 7321, 4421, e 6421.
Também são possíveis códigos com pesos negativos, como 531(-1), 522(-1), 732(-1), e
621(-1). Apesar de possíveis, nenhum destes códigos já foi utilizado em qualquer aplicação
prática. Seu estudo foi meramente teórico.

8 . 4 Códigos de cinco bits ponderados


Várias máquinas (já ultrapassadas) foram desenvolvidas utilizando cinco bits ponderados. O
código 74210, ilustrado na tabela a seguir, é um destes códigos. Ele ainda possui a
vantagem adicional de que todos os dígitos codificados contém dois e somente dois bits em
“um”, o que facilita a verificação se uma palavra pertence ao código ou não.

Dígito decimal Cód. 74210


0 11000
1 00011
2 00101
3 00110
4 01001
5 01010
6 01100
7 10001
8 10010
9 10100
Tabela 8.4 - Código BCD de 5 bits

Os dois “uns” em cada dígito decimal torna possível a detecção de erros simples (erro em um
único bit) em cada dígito. Qualquer dígito que não apresentar “uns”, ou somente um “um”,
ou mais de dois ”uns” pode ser facilmente identificado como errôneo. O valor zero
(codificado como 11000) está contra os pesos utilizados, mas foi assim codificado para
manter a regra dos dois “uns”.

8 . 5 Códigos de sete bits ponderados


Um código de sete bits ponderados foi utilizado pela IBM, no modelo IBM 650. Este
código, de sete bits com pesos 5043210, é também denominado de biquinário. Ele possui
dois grupos de bits, um com dois e outro com cinco bits. Este código também pode ser
considerado como um código “m de n” (veja-se seção 8.8).

8-5
Dígito decimal 50 43210
0 01 00001
1 01 00010
2 01 00100
3 01 01000
4 01 10000
5 10 00001
6 10 00010
7 10 00100
8 10 01000
9 10 10000
Tabela 8.5 - Código BCD de 7 bits

Nos sete bits de qualquer palavra de código, somente dois bits estão em “um”, os demais são
zeros. Além disto, um destes “uns” está no grupo da esquerda (de dois bits) e o outro “um”
está no grupo da direita (de cinco bits). Qualquer código com uma quantidade de “uns”
diferente de dois, ou com mais de um “um” em cada grupo, indica um dígito inválido ou
então um erro. O grupo à esquerda indica se o dígito é menor ou igual a quatro ou então
maior ou igual a cinco. Operações aritméticas podem ser realizadas com relativa facilidade, o
que contribuiu para o seu uso. As operações aritméticas estão baseadas no fato de um dígito
ser incrementado de uma unidade deslocando-se o ‘1’ para a esquerda. Atualmente este
código caiu em desuso, exceto para algumas aplicações específicas.
Um código semelhante é o qui-binário, com pesos 8, 6, 4, 2, 0 / 1, 0.

8 . 6 Códigos Gray (ou códigos cíclicos)


Estes códigos não são utilizados para cálculos aritméticos, mas sim para indicar a variação de
grandezas analógicas (temperatura, ângulo, pressão, voltagem, etc) em forma digital. Estas
grandezas não alteram seus valores de forma brusca, mas sim variam de um valor para outro
de forma contínua. Assim, os códigos de Gray foram desenvolvidos de tal forma que, ao se
avançar de um número para o seguinte, somente um bit do código varia, ou seja, dois
códigos adjacentes se diferenciam somente por uma posição binária. Existem diversas
possibilidades para os códigos cíclicos. O código Gray mais comum (denominado
simplesmente de “código Gray“) é mostrado na Tabela 8.6 a seguir.
Dígito decimal Cód. Gray
0 0000
1 0001
2 0011
3 0010
4 0110
5 0111
6 0101
7 0100
8 1100
9 1101
10 1111
11 1110
12 1010
13 1011
14 1001
15 1000
Tabela 8.6 - Código Gray de 4 bits

8-6
Note-se que todas as 16 combinações possíveis estão presentes; não existem combinações
ilegais ou que indiquem erro. Mas de um código para o seguinte sempre existe somente um
bit que varia o seu valor.

Compare-se, por exemplo, a variação de 3 para 4. Em binário, isto implicaria a variação do


código 0011 para 0100, com a troca de valor de três dígitos binários. No código Gray visto
na Tabela 8.6, a variação é de 0010 para 0110, com a troca de somente um bit. Note-se
também que o código é cíclico, ou seja, do valor 15 para zero (ou vice-versa) também existe
a variação de somente um bit.

Para este código Gray pode-se desenvolver procedimentos de conversão para decimal e
binário. Um número decimal pode ser convertido para Gray convertendo-se primeiramente
este número para binário. A seguir, cada bit é somado em módulo dois com o bit à esquerda
(a soma em módulo dois é uma soma sem ‘vai-um’, ou seja, o resultado somente é um
quando os dois operandos forem diferentes).
Por exemplo, o número decimal 45 é representado por 0101101 em binário. E a conversão
para código Gray produz:

0 1 0 1 1 0 1
∨ ∨ ∨ ∨ ∨ ∨
1 1 1 0 1 1
Assim, o código Gray de 45 é 111011.
Converter do código Gray para decimal é realizado primeiro convertendo-se de Gray para
binário e depois para decimal. A conversão para binário é realizada analisando-se os bits da
esquerda para a direita. Os zeros bits de mais alta ordem são mantidos inalterados até o
primeiro ‘1’, inclusive. A partir daí, os bits zero são invertidos para um até o próximo ‘1’.
Este ‘1’ também é invertido para zero, e os zeros seguintes são copiados inalterados até o
próximo ‘1’ (que também é copiado). Passa-se então à inversão até o próximo ‘1’, a partir
do qual copia-se normalmente até o ‘1’ seguinte, e assim por diante até serem analisados
todos os bits.
Por exemplo, seja o código Gray 001001011. Convertendo-o para binário tem-se:
• 001 Copia-se todos os zeros até o primeiro ‘1’.
• 001110 Inverte-se os bits seguintes até o próximo ‘1’.
• 00111001 Copia-se todos os bits até o ‘1’ seguinte.
• 001110010 Inverte-se os bits até o próximo ‘1’.

Ou seja, obtém-se 001110010, que corresponde em decimal a 114. Note-se que a regra geral
alterna entre cópia dos bits e a inversão dos bits; cada bit ‘em 1’ indica que deve ser realizada
uma troca (deste ‘1’ inclusive).

Este código Gray em particular é um código binário refletido. Um código é dito refletido
quando ele é simétrico (com exceção do bit de mais alta ordem) em relação ao ponto médio
da lista codificada em ordem crescente de valor. Os códigos Gray que não são refletidos são
complexos de serem codificados.

Existem diversos códigos Gray com diferentes tamanhos de ciclo (números de códigos
distintos utilizados). Normalmente eles são referenciados pelo tamanho do seu ciclo e por
uma lista de números. Por exemplo, o código Gray visto acima é referenciado como sendo
de ciclo 16 (1,2,1,3,1,2,1,4,1,2,1,3,1,2,1,4). Os números da lista indicam a posição (a
partir da direita) do bit que é invertido no código atual para formar o próximo código. A
codificação pode iniciar em qualquer número binário (no caso do exemplo, um número de 4
bits). Por exemplo, um código Gray de comprimento 6 (1,2,1,3,2,3) será diferente se
iniciado em números binários diferentes, conforme pode ser visto na tabela a seguir.

8-7
Dígito Codificação inicial em:
decimal 000 001 010 011 100 101 110 111
0 000 001 010 011 100 101 110 111
1 001 000 011 010 101 100 111 110
2 011 010 001 000 111 110 101 100
3 010 011 000 001 110 111 100 101
4 110 111 100 101 010 011 000 001
5 100 101 110 111 000 001 010 011
6 000 001 010 011 100 101 110 111
Tabela 8.7 - Codificação de código Gray

Assim, para caracterizar inequivocamente um código Gray, além do comprimento e da


seqüência de formação, também deve-se indicar a codificação inicial. Uma lista de alguns
códigos possíveis é fornecida abaixo:
• para dois bits: Comprimento 4 (1,2,1,2)
• para três bits: Comprimento 6 (1,2,1,3,2,3)
Comprimento 6 (1,2,3,1,2,3)
Comprimento 8 (1,2,1,3,1,2,1,3)
• para quatro bits: Comprimento 8 (1,2,3,4,1,4,3,2)
Comprimento 10 (1,2,1,3,4,3,1,2,1,4)
Comprimento 10 (1,2,1,3,4,1,2,1,3,4)

Um código Gray de 5 bits de comprimento 10, comumente utilizado em operações de


contagem, é o “código caminhante” (do inglês “walking code” ou “creeping code”):

Dígito Código
0 00000
1 00001
2 00011
3 00111
4 01111
5 11111
6 11110
7 11100
8 11000
9 10000
Tabela 8.8 - Código Gray de 5 bits

A razão para o uso deste código está na sua facilidade de implementação. Para avançar o
contador de uma unidade, basta mover todos os bits para a esquerda, inverter o bit mais
significativo e colocá-lo na posição mais à direita (bit menos significativo).

8 . 7 Códigos de detecção e correção de erros

Quando informação é transferida de uma seção para outra do computador, ou mesmo entre
dois computadores, não é incomum a ocorrência de erros, sejam eles devidos a ruídos
eletromagnéticos ou ao mau funcionamento de um componente. Se uma codificação binária
de ‘n’ bits utiliza todas as combinações possíveis (2n), então erros não podem ser nem
detectados nem corrigidos. Como todas as combinações são válidas, não existem
combinações inválidas. Um erro sempre irá transformar uma codificação válida em outra
codificação também válida.

Para ser possível a detecção de erros, e até mesmo sua correção, deve-se introduzir
redundância na codificação, seja na forma de bits extras ou do uso de combinações inválidas.

8-8
Três técnicas principais serão analisadas nas seções seguintes: códigos m-de-n, códigos com
paridade e códigos de Hamming. Todos estes códigos utilizam o mesmo comprimento em
bits para sua codificação. Existem códigos de comprimento variável, em que os símbolos
mais freqüentes utilizam menos bits que os símbolos menos freqüentes. Assim, por
exemplo, a codificação da letra “e” utilizaria muito menos bits que a letra “q”. Estes códigos,
como por exemplo código Morse, código de Shannon-Fano, código de Huffman, não serão
analisados aqui.

8 . 8 Códigos m-de-n

Os códigos m-de-n são compostos de tal forma que os ‘n’ bits de uma palavra binária são
formados por ‘m’ “uns” e ‘n-m’ “zeros”. Com esta limitação, de 2n combinações binárias
possíveis somente (n/m) combinações, ou seja, n!/m!(n-m)!. Este número de combinações é
maximizado quando m=n/2 (ou m=n/2 ± 1/2, se n for ímpar).

No código 2-de-5, onde palavras de 5 bits apresentam sempre 2 bits em ‘1’, tem-se então
exatamente 5!/2!(5-2)! = 5!/2!3! = 5.4/1.2 = 10 combinações válidas. Este código já foi
analisado na seção 7.3 (códigos de cinco bits ponderados, com pesos 74210). Outra
codificação possível é a 2-de-7, o código biquinário. Este código já foi analisado na seção
8.5 (códigos de sete bits ponderados, com pesos 5043210).

8 . 9 Códigos de paridade
Nestes códigos é adicionado um bit à palavra codificada, de tal forma que o número de “uns”
seja par (paridade par) ou ímpar (paridade ímpar). A tabela a seguir ilustra o uso de bits de
paridade par e ímpar para um código de três bits.

Código Paridade par Soma dos ‘1’ Paridade ímpar Soma dos ‘1’
000 0 0 1 1
001 1 2 0 1
010 1 2 0 1
011 0 2 1 3
100 1 2 0 1
101 0 2 1 3
110 0 2 1 3
111 1 4 0 3
Tabela 8.9 - Códigos de paridade

Se o código utilizado for de paridade par, todas as palavras com um número ímpar de “uns”
podem ser rejeitadas e sinalizadas como errôneas. Se o código for de paridade ímpar,
qualquer palavra com um número par de “uns” estará errada.

Assim, pode-se detectar qualquer erro simples (inversão de um bit). Mas não existe neste
código a capacidade de detectar dois ou mais erros (como por exemplo um erro que
transforme o código 011001 em 101001 – o número de uns permanece ímpar, mas a palavra
foi alterada). Também não existe a capacidade de corrigir o bit errôneo.

8 . 1 0 Códigos de Hamming

Os códigos de Hamming, desenvolvidos por R.W. Hamming, introduzem vários bits de


paridade, que possibilitam não só a detecção de erros mas também a sua correção. Um
conceito importante para tal é a “distância de Hamming”, ou seja, o número de bits que são
alterados entre dois códigos adjacentes.

8-9
Por exemplo, se estes dois códigos são 0001 e 0100, sua distância é de 2; entre os códigos
0000 e 1111 a distância é de 4. Se a distância de Hamming for de 1, como por exemplo em
000, 100, 101, 001, 011, 111, 110 e 010, não é possível nem a detecção nem a correção de
erros; cada erro transforma um código válido em outro também válido. Se a distância for de
2, como por exemplo em 000, 011, 110 e 101, então é possível detectar-se um erro, mas que
não pode ser corrigido (por exemplo, 001 poderia ter sido originado tanto de 000 como de
101 ou 011). Se a distância for de 3, como por exemplo em 011 e 100, então um erro
simples pode ser detectado e corrigido (por exemplo, 000 só poderia ter sido originado de
100), e um erro duplo pode ser detectado.

O código de Hamming corrige um erro alterando a palavra errada para o código válido mais
próximo (de menor distância), desde que esta menor distância seja inequívoca, isto é, não
existam duas ou mais palavras corretas à igual distância da palavra errada.

Para possibilitar a correção de um erro simples, o código de Hamming mostrado na tabela a


seguir utiliza três bits adicionais de paridade (A, B e C) para os quatro bits de código
(ponderados por 8, 4, 2 e 1).

Posição 1 2 3 4 5 6 7
Código A B 8 C 4 2 1
0 0 0 0 0 0 0 0
1 1 1 0 1 0 0 1
2 0 1 0 1 0 1 0
3 1 0 0 0 0 1 1
4 1 0 0 1 1 0 0
5 0 1 0 0 1 0 1
6 1 1 0 0 1 1 0
7 0 0 0 1 1 1 1
8 1 1 1 0 0 0 0
9 0 0 1 1 0 0 1
10 1 0 1 1 0 1 0
11 0 1 1 0 0 1 1
12 0 1 1 1 1 0 0
13 1 0 1 0 1 0 1
14 0 0 1 0 1 1 0
15 1 1 1 1 1 1 1
Tabela 8.10 - Código de Hamming

Neste caso, o bit ‘A’ é usado para calcular a paridade par das posições 1, 3, 5 e 7. O bit ‘B’
é usado para obter-se paridade par nas posições 2, 3, 6 e 7 enquanto que ‘C’ realiza a
paridade par para as posições 4, 5, 6 e 7.

Existindo um erro, a posição do bit em erro é calculada por ‘cba’, onde ‘c’, ‘b’ e ‘a’ serão
zeros se a paridade calculada por ‘C’, ‘B’ e ‘A’ for par ou “uns” se a paridade for ímpar. Por
exemplo, se o número seis (1100110) for recebido erroneamente como 1100010, tem-se:
• verificação das posições 4, 5, 6 e 7: paridade ímpar: c = 1
• verificação das posições 2, 3, 6 e 7: paridade par: b = 0
• verificação das posições 1, 3, 5 e 7: paridade ímpar: a = 1
Assim, ‘cba’ = 101, ou seja, a posição 5 está errada e seu bit deve ser invertido para ser
corrigido. Se o número tivesse sido recebido correto, a combinação dos bits de paridade
seria zero (000).

Esta codificação pode ser estendida para qualquer número ‘n’ de bits de informação, desde
que ‘k’ bits de paridade sejam adicionados de acordo com a fórmula 2k ≥ n + k + 1. A tabela
a seguir ilustra alguns casos.

8-10
Bits de informação Bits de paridade
4 3
5 4
11 4
12 5
26 5
27 6
57 6
Tabela 8.11 - Bits de paridade no código de Hamming

8 . 1 1 Códigos alfabéticos (ou códigos alfanuméricos)


Os computadores devem manipular não somente informação numérica, mas também
informação alfabética, constituída de caracteres, sinais de pontuação e dígitos numéricos. Os
códigos que permitem este tipo de representação são denominados de códigos alfanuméricos.
Os primeiros códigos desenvolvidos utilizavam 6 bits, o que possibilitava a representação de
todos as letras maiúsculas (26), os dígitos decimais (10) e mais uma série de caracteres
especiais (ponto, vírgula, dois pontos, etc). Um destes códigos é o Hollerith, utilizado pela
IBM em cartões perfurados. Note-se que não havia a previsão para letras minúsculas.

Rapidamente os códigos foram estendidos para sete bits (inclusão das letras minúsculas) e
posteriormente para oito bits. Os dois códigos mais amplamente utilizados são o ASCII
(American Standard Code for Information Interchange) e o EBCDIC (Extended Binary
Coded Decimal Interchange Code). As letras são codificadas de tal forma a facilitarem a
ordenação alfabética de informação textual através da simples ordenação binária crescente.

O código ASCII de sete bits (com 128 combinações possíveis) é mostrado na Tabela 8.12.
Os códigos de 0 a 31 são reservados para caracteres de controle, como por exemplo
tabulação (ht), retorno de carro (cr), ejeção de página (ff), aviso sonoro (bel), retrocesso
(bsp), avanço de linha (lf), tabulação vertical (vt), escape (esc) e outros. Os caracteres
visíveis vão do código 32 (espaço em branco) até o 126 (caracter til). Note-se que dígitos e
letras estão em ordem numérica crescente (mas não contínua entre si), que não existe
codificação de nenhum caracter acentuado, e que letras maiúsculas e minúsculas possuem
uma diferença numérica de 32.

Bits Bits superiores (mais significativos)


inferiores 000 001 010 011 100 101 110 111
0000 null dle 0 @ P ` p
0001 soh dc1 ! 1 A Q a q
0010 stx dc2 " 2 B R b r
0011 etx dc3 # 3 C S c s
0100 eot dc4 $ 4 D T d t
0101 enq nak % 5 E U e u
0110 ack syn & 6 F V f v
0111 bell etb ' 7 G W g w
1000 bsp can ( 8 H X h x
1001 ht em ) 9 I Y i y
1010 lf sub * : J Z j z
1011 vt esc + ; K [ k {
1100 ff fs , < L \ l |
1101 cr gs – = M ] m }
1110 so rs . > N ^ n ~
1111 si us / ? O _ o del
Tabela 8.12 - Código ASCII de 7 bits

8-11
O código ASCII foi posteriormente estendido para oito bits, com a incorporação de mais 128
combinações à tabela acima. Com isto pode-se representar caracteres acentuados (á, à, â, ä,
ã, å, ñ, ç, etc) e diversos símbolos específicos de diversas línguas. Não existe, entretanto,
uma definição única para esta tabela ASCII de 8 bits; cada fabricante desenvolveu a sua
própria.

Atualmente oito bits já se tornaram insuficientes para representar todos os símbolos


utilizados. Não há espaço disponível, por exemplo, para codificação do alfabeto grego, árabe
ou cirílico (para não mencionar os alfabetos hindus, japoneses e chineses). Para solucionar o
problema, estuda-se atualmente um código de 16 bits, que possibilitaria representar 65536
símbolos distintos (Unicode).

8-12
Capítulo
NOVE
Elementos básicos de organização

9 . 1 Introdução

Um computador pode ser projetado e/ou descrito em diversos níveis de abstração. Assim,
pode-se descrever inteiramente um computador através de equações booleanas ou o seu
equivalente em portas lógicas E, OU e NOT. Devido à complexidade dos computadores
atuais, entretanto, tal nível de abstração não é prático, por envolver milhares de equações.
Uma das soluções empregadas para diminuir o número de elementos a serem manipulados
envolve o uso de níveis de abstração mais elevados, tal como o nível de transferência entre
registradores (em inglês Register Transfer, ou RT). Este capítulo aborda os principais
elementos utilizados neste nível.

9 . 2 Portas lógicas e equações booleanas


A álgebra booleana é definida sobre um conjunto de dois elementos, denominados de
verdadeiro e falso, ou 0 e 1. Em sistemas digitais, estes dois valores são dois níveis de
tensão pré-fixados aos quais são associados os nomes 0 e 1.
O complemento de uma variável booleana é definido pela tabela-verdade indicada na Tabela
9.1. Basicamente tem-se uma alternância entre os dois valores.
Entrada a Saída s
0 1
1 0
Tabela 9.1 - Tabela-verdade da operação de complemento
Em termos de portas lógicas, esta operação é realizada por uma porta NOT (ou NÃO), com a
representação gráfica lustrada na Figura 9.1. Nas equações a serem utilizadas neste livro será
usado o símbolo de apóstrofe (’) para indicar a operação de complementação. Assim, a’
indica o complemento da variável a.

a s

Figura 9.1 - Porta NOT

Existem duas operações básicas na álgebra booleana, que são chamadas de AND (E) e OR
(OU). As tabelas-verdade destas duas operações podem ser vistas nas Tabelas 9.2 e 9.3,
respectivamente. Nas equações utiliza-se o símbolo ‘.’ para a operação AND e o símbolo ‘+’
para a operação OR. Assim, a equação (a e b) ou c será descrita como a.b + c, ou até de
forma mais simples como ab + c (omitindo o ‘.’ da operação AND e simplesmente
escrevendo-se as duas variáveis juntas).

9-1
Entrada a Entrada b Saída s
0 0 0
0 1 0
1 0 0
1 1 1
Tabela 9.2 - Tabela verdade da operação AND

Entrada a Entrada b Saída s


0 0 0
0 1 1
1 0 1
1 1 1
Tabela 9.3 - Tabela verdade da operação OR

Em termos de portas lógicas, as operações de AND e OR são representadas pelas portas


mostradas nas Figuras 9.2 e 9.3, respectivamente. As portas lógicas implementam
exatamente a função booleana correspondente, mas possuem um atributo adicional: elas não
são instantâneas, mas necessitam de um certo tempo para operar. Isto faz que uma mudança
em uma das entradas não se reflita instantaneamente na saída, mas somente vá ocorrer após
um determinado tempo, denominado de tempo de propagação. Quanto menor for este tempo,
mais rápido o circuito irá responder às mudanças nas suas entradas, e por conseqüência
maior será sua freqüência de operação.

Note-se que nas Figuras 9.2 e 9.3 considerou-se somente portas de duas entradas. Na
realidade, o número de entradas de uma porta pode ser variável. O número de portas é
aumentado simplesmente usando-se a propriedade associativa das operações AND e OR.

a
s
b

Figura 9.2 - Porta lógica AND de duas entradas

a
s
b

Figura 9.3 - Porta lógica OR de duas entradas

Um determinada expressão lógica pode ser expressa em termos de uma tabela-verdade, de


uma equação booleana ou até mesmo em termos de portas lógicas. A Tabela 9.4 ilustra a
tabela-verdade e as portas lógicas para a equação booleana a + b.c’.

a b c s
0 0 0 0
0 0 1 0
0 1 0 1
0 1 1 0
1 0 0 1
1 0 1 1
1 1 0 1
1 1 1 1
Tabela 9.4 - Tabela-verdade da função a+bc’

9-2
c c’

b.c’
b
s
a

Figura 9.4 - Portas lógicas para a função a+bc’

A construção de uma tabela-verdade é bem simples. Constrói-se inicialmente uma tabela,


considerando-se todas as combinações possíveis das variáveis de entrada (a, b e c na Tabela
9.4). A seguir, para cada linha calcula-se o valor booleano da saída (ou saídas, se existirem
mais de uma). Note-se que o número de linhas de uma tabela é a potência de dois do número
de variáveis de entrada. Assim, no caso da Tabela 9.4, tem-se 3 entradas e,
consequentemente, 8 linhas.

A obtenção de um diagrama de portas lógicas a partir de uma equação booleana também é


simples. Para cada operador da equação (e, ou, complemento) existe uma relação direta em
termos de portas lógicas (AND, OR, NOT). Note-se que, quanto mais simples for a equação
booleana, menor o número de portas lógicas necessárias. Portanto, é muito importante
simplificar uma equação antes de transformá-la em portas lógicas. A Tabela 9.5 e a Figura
9.5 ilustram este processo. Para facilitar a compreensão, foram adicionadas colunas à tabela-
verdade, que representam os valores intermediários (parciais) da equação.
a b a’ b’ a’+b’ b(a’+b’) a+b(a’+b’)
0 0 1 1 1 0 0
0 1 1 0 1 1 1
1 0 0 1 1 0 1
1 1 0 0 0 0 1
Tabela 9.5 - Tabela verdade da função a+b(a’+b’)

a a’
a’+b’
a+b(a’+b’)
b b’

b(a’+b’)

Figura 9.5 - Portas lógicas para a função a+b(a’+b’)

Tanto por observação da tabela-verdade da Tabela 9.5 como por simplificação da equação
booleana chega-se na equação a+b, que pode ser implementada através de uma única porta
OR e representa uma grande economia de portas em relação à implementação. Existem
inúmeras técnicas de simplificação de equações booleanas, o que, entretanto, não será tratado
neste livro. A minimização de uma equação booleana é importante em sistemas digitais por
diversos motivos. Entre os principais destacam-se:

• redução do número de portas lógicas necessárias para implementação da função – quanto


menos portas lógicas forem utilizadas, mais econômico será o circuito.

9-3
• redução do número de entradas de uma porta lógica – portas com menor número de
entradas são mais econômicas; assim, por exemplo, uma porta AND de três entradas é
melhor que uma porta AND de quatro entradas (e uma de duas entradas é melhor ainda!).
• redução da potência consumida pelo circuito.
• redução da área física necessária para o circuito.
• diminuição do tempo necessário para que uma mudança em um ou mais entradas se
manifeste na saída do circuito (tempo de propagação).

Além das portas tradicionais (AND, OR, NOT), ainda são utilizadas diversas outras. De
especial interesse são as portas NAND (não-e), NOR (não-ou), XOR (ou exclusivo) e
XNOR (não-ou exclusivo). As tabelas-verdade para estas portas estão mostradas nas Tabelas
9.6 a 9.9, respectivamente.
Entrada a Entrada b Saída s
0 0 1
0 1 1
1 0 1
1 1 0
Tabela 9.6 - Tabela-verdade da operação NAND
Entrada a Entrada b Saída s
0 0 1
0 1 0
1 0 0
1 1 0
Tabela 9.7 - Tabela-verdade da operação NOR
Entrada a Entrada b Saída s
0 0 0
0 1 1
1 0 1
1 1 0
Tabela 9.8 - Tabela-verdade da operação XOR
Entrada a Entrada b Saída s
0 0 1
0 1 0
1 0 0
1 1 1
Tabela 9.9 - Tabela-verdade da operação XNOR
Em termos de portas lógicas, a porta NAND é simplesmente uma porta AND seguida de um
inversor, como ilustrado na Figura 9.6, assim como uma porta NOR é uma porta OR
seguida de um inversor, como mostrado na Figura 9.7. Estas duas portas possuem grande
interesse na área de sistemas digitais porque, dependendo da tecnologia empregada para a
fabricação de circuitos integrados, elas podem ser implementadas de forma mais simples e
econômica que as portas AND e OR.

a a
s=(a.b)’ a.b s=(a.b)’
b b

Figura 9.6 - Porta lógica NAND de duas entradas e seu equivalente em termos de AND e
NOT

9-4
a a
s=(a+b)’ a+b s=(a+b)’
b b

Figura 9.7 - Porta lógica NOR de duas entradas e seu equivalente em termos de OR e NOT

A operação de ou-exclusivo (Figura 9.8) é derivada do ou, mas com saída em zero no caso
das duas entradas terem valor um. Esta pequena diferença torna o ou-exclusivo uma porta
com características únicas. Sua função pode ser interpretada como soma em módulo 2 (onde
o carry-out é desprezado), ou como gerado de paridade par, ou simplesmente como o
operador diferença.
a a
a⊕b s=(a⊕b)’
b b

Figura 9.8 - Porta lógica XOR de duas entradas e porta lógica XNOR de duas entradas

9 . 3 Equivalência de portas lógicas

Uma porta lógica pode ser substituída por um conjunto de portas equivalentes, ou seja, um
conjunto de portas que desempenha exatamente a mesma função booleana. Assim, por
exemplo, uma porta NAND é equivalente a uma porta AND seguida de um inversor (porta
NOT). A Tabela 9.10 ilustra algumas destas equivalências.

Expressão ou porta Expressão equivalente


a+b (a’ . b’)’
a’ nand b’
a.b (a’+ b’)’
a’ nor b’
a xor b a.b’ + a’.b
a’ (a.a)’, a nand a
(a+a)’, a nor a
a xor 1
a xnor b a.b + a’.b’
Tabela 9.10 - Equivalência de funções booleanas e portas lógicas

Estas equivalências permitem que uma equação booleana qualquer possa ser descrita somente
com o uso de AND e NOT, por exemplo. Onde fosse necessário utilizar o operador OR, ele
seria substituído pelo seu equivalente. Com isto obtêm-se os vários conjuntos mínimos de
funções. Entre eles podem ser citados:
• somente portas NAND - a partir do NAND pode-se obter um inversor, e a partir deste
uma porta AND e posteriormente uma porta OR.
• somente portas NOR - a partir do NOR obtém-se o inversor, o OR e o AND, pelo
mesmo raciocínio utilizado para as portas NAND acima.
• somente portas AND e NOT
• somente portas OR e NOT
• somente portas AND e XOR
Dependendo de quais portas lógicas são utilizadas, existem várias formas de representação
de uma mesma equação booleana. Duas formas são particularmente úteis, por serem
facilmente minimizáveis utilizando-se álgebra booleana, e por produzirem uma estrutura de
portas lógicas bem regular: a soma de produtos e o produto de somas.

9-5
Na soma de produtos, as variáveis de entrada são agrupadas inicialmente em termos-produto
(portas AND), e estes termos são posteriormente somados (portas OR) para compor a
equação final. No produto de somas, as variáveis de entrada são agrupadas em termos-soma
(portas OR) e depois multiplicadas (portas AND) para formar a equação final. Tanto nos
termos-produto como nos termos-soma, as variáveis de entrada podem aparecer ou na forma
normal ou na forma complementada. A forma de soma de produtos, assim como a de
produto de somas, pode ser facilmente derivada de uma tabela verdade, como ilustra o
procedimento abaixo:

1. Construir a tabela-verdade com as entradas e saídas do circuito.


2. Acrescentar uma coluna que contenha, para cada uma das linhas das possíveis
combinações de entrada, um termo-produto formado pelo ‘e’ lógico de todas as variáveis
de entrada. Se o valor da variável de entrada for igual a zero (naquela linha da tabela), ela
aparece complementada no termo-produto. Se o valor da variável de entrada for igual a
um, ela aparece na forma normal (sem ser complementada) no termo-produto.
3. Construir uma soma de produtos, na qual aparecem todos os termos-produto
correspondentes a valores de saída iguais a 1.
4. Simplificar a expressão obtida, aplicando as propriedades da álgebra booleana.

O procedimento para a formação de um produto de soma é análogo, conforme pode ser visto
no procedimento abaixo:

1. Construir a tabela-verdade com as entradas e saídas do circuito.


2. Acrescentar uma coluna que contenha, para cada uma das linhas das possíveis
combinações de entrada, um termo-soma formado pelo ‘ou’ lógico de todas as variáveis
de entrada. Se o valor da variável de entrada for igual a zero (naquela linha da tabela), ela
aparece na forma normal no termo-soma. Se o valor da variável de entrada for igual a
um, ela aparece complementada no termo-soma.
3. Construir um produto de somas, na qual aparecem todos os termos-soma
correspondentes a valores de saída iguais a 0.
4. Simplificar a expressão obtida, aplicando as propriedades da álgebra booleana.
A Tabela 9.11 apresenta um exemplo ao qual são aplicados os dois procedimentos descritos
acima.
a b c s termos-produto termos-soma
0 0 0 1 a’.b’.c’ a+b+c
0 0 1 0 a’.b’.c a+b+c’
0 1 0 1 a’.b.c’ a+b’+c
0 1 1 0 a’.b.c a+b’+c’
1 0 0 1 a.b’.c’ a’+b+c
1 0 1 0 a.b’.c a’+b+c’
1 1 0 1 a.b.c’ a’+b’+c
1 1 1 0 a.b.c a’+b’+c’
Tabela 9.11 - Exemplo de formação de soma de produtos e produto de somas

Formando-se a soma de produtos, obtém-se:


s = a’.b’.c’ + a’.b.c’ + a.b’.c’+ a.b.c’
Para o produto de somas, obtém-se:
s = (a+b+c’).(a+b’+c’).(a’+b+c’).(a’+b’+c’)
Ambas equações podem agora ser simplificadas, e em ambos os casos chega-se a mesma
forma mínima:
s = c’

9-6
Observe-se que a forma soma de produtos pode ser implementada utilizando-se somente
portas NAND no lugar das portas AND e OR, como pode ser visto na Figura 9.9. Da mesma
forma, o produto de somas pode ser implementado com portas NOR no lugar das portas
AND e OR.

a a
b b
s s
c c

d d

Figura 9.9 - Soma de produtos através de portas NAND

9 . 4 Circuitos combinacionais

Circuitos combinacionais são aqueles que não possuem memória ou quaisquer outros
elementos de armazenamento. Suas saídas são função única e exclusivamente das entradas.
São construídos por portas lógicas sem realimentação, isto é, o valor das saídas não é
utilizado em qualquer outra parte do circuito.

Dois circuitos combinacionais bem simples, bastante utilizados em sistemas digitais, são os
multiplexadores e os decodificadores. Uma unidade lógica e aritmética (ULA ou UAL), por
outro lado, é bem mais complexa, e será vista em seções posteriores.
Um multiplexador (ou seletor) é um circuito combinacional que possui m entradas e uma
saída. A cada instante de tempo, o valor da saída é igual ao valor de uma das entradas,
conforme determinado por um conjunto de linhas de controle (ou linhas de seleção). A
Tabela 9.12 ilustra alguns casos de multiplexadores.

Multiplexador Número de entradas Número de linhas de seleção


2-para-1 2 1
4-para-1 4 2
8-para-1 8 3
16-para-1 16 4
Tabela 9.12 - Multiplexadores típicos

Um multiplexador pode ser descrito através de um comando case:


case seleção of
0: saída := entrada0 ;
1: saída := entrada1 ;
2: saída := entrada2 ;
...
m: saída := entradam;
end;
No caso mais simples de um multiplexador de 2-para-1, a descrição também pode ser feita
através de um comando if-then-else:
if seleção=0
then saída := entrada0
else saída := entrada1 ;

9-7
Em termos de equação booleana ou portas lógicas, um multiplexador é bem simples. A
seguir é ilustrado o caso de um multiplexador de 2-para-1; os demais podem ser construídos
utilizando-se exatamente a mesma metodologia.

Entrada0 (a) Entrada1 (b) Seleção (sel) Saída (s)


0 0 0 0
0 0 1 0
0 1 0 0
0 1 1 1
1 0 0 1
1 0 1 0
1 1 0 1
1 1 1 1
Tabela 9.13 - Tabela-verdade de um multiplexador de 2-para-1

Extraindo-se da Tabela 9.13 a equação booleana através de uma soma de produtos, obtém-
se:

s = a’.b.sel + a.b’.sel’ + a.b.sel’ + a.b.sel

A implementação através de portas lógicas iria necessitar de 3 inversores, quatro ANDs de


três entradas e um OR de quatro entradas. Simplificando-se a equação, entretanto, obtém-se:
s = (a’.b + a.b).sel + (a.b’ + a.b).sel’

s = a.sel’ + b.sel
Esta equação pode ser implementada através de 2 postas AND de duas entradas, 1 porta OR
de duas entradas e 1 inversor, conforme mostra a Figura 9.10.

sel

Figura 9.10 - Portas lógicas de um multiplexador de 2-para-1


É interessante notar que uma tabela-verdade pode ser simplificada se for utilizado um novo
valor – o don’t care, ou “não interessa”, representado por um ‘X’. Uma linha da tabela onde
uma variável apresenta o valor X indica que, para o caso desta linha, o valor da variável não
interessa para a saída, ou seja, a variável não influencia o valor da saída neste caso.

A Tabela 9.14 mostra a tabela-verdade de um multiplexador 2-para-1 utilizando-se este novo


valor.
Entrada0 (a) Entrada1 (b) Seleção (sel) Saída (s)
0 X 0 0
1 X 0 1
X 0 1 0
X 1 1 1
Tabela 9.14 - Tabela-verdade de um multiplexador de 2-para-1

9-8
Variáveis com X não participam de um termo-produto quando a soma de produtos é extraída
da tabela. Note-se que, no caso ilustrado na Tabela 9.14, a equação booleana derivada da
tabela já é diretamente a equação booleana simplificada.

Um multiplexador é representado como indicado na Figura 9.11. As duas representações são


possíveis; a literatura especializada usa inclusive outras representações. Neste livro serão
utilizadas indistintamente qualquer uma destas duas representações.

a a

s s

b b

sel sel

Figura 9.11 - Representação simbólica de um multiplexador de 2-para-1


Um multiplexador pode ser expandido para trabalhar com entradas de n bits, ao invés de
entrada de 1 bit como foi o caso até agora. Basta utilizar n multiplexadores em paralelo, com
a linha de seleção sendo comum a todos os multiplexadores. O desenho utilizado é o mesmo,
mas agora as linhas de entrada e saída representam n bits, ao invés de um só. Para melhor
compreensão, a dimensão de cada linha, em bits, é indicada na figura. A Figura 9.12 ilustra
o caso de um multiplexador de 4-para-1 para dados de 8 bits.

a 8 a 8

b 8 b 8
8 s 8 s
c 8 c 8

d 8 d 8

sel 2 sel 2

Figura 9.12 - Representação de um multiplexador de 4-para-1, de 8 bits


Outro circuito combinacional de interesse para a organização de computadores é o
decodificador. Na sua forma mais geral, é um circuito com n entradas e 2n saídas. Se o valor
codificado nas entradas é b, então todas as saídas estão em nível zero e somente a saída de
índice b está em nível um. Um decodificador aparece mais comumente nas formas de 2-para-
4, 3-para-8 e 4-para-16. A Tabela 9.15 mostra a tabela-verdade de um decodificador de 2-
para-4, e a Figura 9.13 mostra uma possível implementação.

Entrada 0 Entrada 1 Saída 0 Saída 1 Saída 2 Saída 3


0 0 1 0 0 0
0 1 0 1 0 0
1 0 0 0 1 0
1 1 0 0 0 1
Tabela 9.15 - Tabela-verdade de um decodificador de 2-para-4

9-9
saída 0

entrada 1
saída 1

entrada 0
saída 2

saída 3

Figura 9.13 - Implementação de um multiplexador de 2-para-4


Um decodificador é comumente empregado para transformar informação codificada em 1, 2,
3 ou 4 bits em 2, 4, 8 ou 16 linhas, respectivamente. O exemplo mais típico do seu emprego
é na decodificação de uma instrução.

9 . 5 Circuitos seqüenciais
Circuitos seqüenciais são aqueles que possuem memória. Suas saídas são função tanto das
entradas como dos valores da saída. Dito de outra maneira, nos circuitos seqüenciais o novo
valor da saída dependo do estado atual destas saídas.
Dois circuitos seqüenciais bastante utilizados são os registradores e os contadores. Ambos
são construídos com flip-flops, ou seja, registradores capazes de armazenar um único bit.
Dependendo da maneira exata como é controlado, um flip-flop recebe várias denominações
distintas.

O flip-flop mais simples é o tipo RS. Possui duas entradas, R (de reset, ou desligar) e S (de
set, ou ligar). A ativação do sinal S coloca a saída do flip-flop em nível 1, e a ativação do
sinal R leva a saída ao nível lógico 0. A Figura 9.14 mostra duas possíveis implementações
de um flip-flop RS, uma com portas NOR e outra com portas NAND. Note-se que para
portas NOR a ativação de R e de S se faz com nível lógico 1, enquanto que com portas
NAND a ativação de R e de S se faz com nível lógico 0 (o que é indicado pelo uso de R’ e
S’).
S’ R
Q Q

R’ S
Q’ Q’

Figura 9.14 - Flip-flop RS com portas NAND e portas NOR

9-10
A Tabela 9.16 mostra a variação dos valores dos sinais de saída (Q e Q’) de acordo com os
sinais de entrada (R e S) ao longo de nove intervalos de tempo. A Figura 9.15 reproduz a
mesma informação em um diagrama de tempos.

t R S Q Q’
1 0 0 0 1
2 0 1 1 0
3 0 0 1 0
4 1 0 0 1
5 0 0 0 1
6 1 0 0 1
7 0 0 0 1
8 0 1 1 0
9 0 0 1 0
Tabela 9.16 - Variação de sinais em um flip-flop RS

Q’

t 1 2 3 4 5 6 7 8 9
Figura 9.15 - Diagrama de tempos em um flip-flop RS

Observe-se que, quando as entradas não estão ativas, um flip-flop RS mantém seu estado
anterior, ou seja, memoriza o último valor lógico que foi armazenado nele, seja via um
comando S (set) ou R (reset). Note-se também que a ativação de ambas as entradas
simultaneamente leva a resultados imprevisíveis. Assim, o funcionamento de um flip-flop
RS pode ser resumido de acordo com a Tabela 9.17, onde Qt indica o estado atual e Qt+1
indica o próximo estado.
R S Qt+1 Resultado
0 0 Qt Estado fica inalterado
0 1 1 Estado passa para 1
1 0 0 Estado passa para 0
1 1 Indeterminado Condição de erro
Tabela 9.17 - Tabela de um flip-flop RS

Um flip-flop RS pode armazenar um valor, mas o seu controle é complicado pelo fato de ser
sempre sensível a qualquer variação de valor nas entradas R e S. Isto levou à criação de um
flip-flop que pudesse ser insensível às entradas em determinados momentos. Para isto foi
introduzida uma terceira entrada, denominada de controle, clock (relógio) ou carga.
Enquanto a entrada de controle estiver desabilitada (C=0), o estado do flip-flop ficará
indiferente às entradas R e S. A idéia deste flip-flop é sincronizar a mudança do seu estado,
isto é, restringi-la a certos instantes. Uma implementação possível para este flip-flop pode
ser vista na Figura 9.16.

9-11
S
Q

Q’
R

Figura 9.16 - Flip-flop RS com controle

Para eliminar a situação não permitida de R e S ativos simultaneamente, pode-se interligá-los


através de um inversor, como mostra a Figura 9.17. Neste caso R e S sempre terão sentidos
opostos. Elimina-se a situação de R e S ativos simultaneamente, mas também se elimina a
situação de R e S ambos estarem inativos. O resultado é um flip-flop que copia o valor lógico
da entrada D (de Dado) quando o controle estiver ativo.

D
Q

Q’

Figura 9.17 - Flip-flop D sensível ao nível


Com o flip-flop D tem-se o elemento básico de armazenamento, que copia o valor da entrada
(D) quando o sinal de controle ou carga (C) é ativado. No caso do flip-flop da Figura 9.17,
enquanto o valor da entrada de controle C for igual a 1, o valor da entrada D é copiado (ou
armazenado) para o flip-flop. Se a entrada D variar enquanto C=1, o valor armazenado
também varia. Quando C=0, o flip-flop mantém seu valor, independente de variações em D.
Com isto tem-se um flip-flop sensível ao nível, ou um latch.

Considerando o instante de ativação do sinal de controle, podem ser definidos quatro tipos
distintos:
• Sensível ao nível 1 - o sinal de controle é ativo enquanto apresentar nível 1, e por
todo o tempo que permanecer neste nível.
• Sensível ao nível 0 - o sinal de controle é ativo enquanto apresentar nível 0, e por
todo o tempo que permanecer neste nível.
• Sensível à borda de subida - o sinal de controle é ativo quando realizar uma
transição do nível 0 para o nível 1, e somente neste instante de tempo.
• Sensível à borda de descida - o sinal de controle é ativo quando realizar uma
transição do nível 1 para o nível 0, e somente neste instante de tempo.

9-12
Flip-flops sensíveis à borda são mais complexos que os sensíveis ao nível. A Figura 9.18
ilustra o caso de um flip-flop D sensível à borda de subida.

Q’

Figura 9.18 - Flip-flop D sensível à borda


Além do flip-flop tipo D, também são bastante utilizados o flip-flop tipo T (toggle), que
muda de estado a cada ativação do sinal de controle, e o flip-flop JK, que possui duas
entradas (J e K). A Tabela 9.18 ilustra o comportamento de cada um destes três flip-flops.
Note-se que o sinal de controle “ativo” pode significar qualquer uma das quatro situações
analisadas acima.
Tipo D Tipo T Tipo JK

D C Qt+1 C Qt+1 J K C Qt+1


X inativo Qt inativo Qt X X inativo Qt
D ativo D ativo Qt’ 0 0 ativo Qt
0 1 ativo 1
1 0 ativo 0
1 1 ativo Qt’
Tabela 9.18 - Flip-flops D, T e JK
Um conjunto de n flip-flops pode ser interconectado para formar um registrador de n bits, ou
seja, um registrador capaz de armazenar n bits. Um registrador deste tipo é obtido usando
entradas D independentes para cada bit e um sinal de controle comum para todos os bits,
como mostra a Figura 9.21. Neste caso denomina-se este registrador de registrador de carga
e saída paralelas.

9-13
Dn-1 Dn-2 ....... D1 D0

Flip-flop Flip-flop ....... Flip-flop Flip-flop


D D D D

C
Qn-1 Qn-2 ....... Q1 Q0
Figura 9.19 Registrador de n bits de carga e saída paralelas

Um registrador, além de armazenar um conjunto de bits, também pode ser utilizado para
efetuar algumas operações sobre estes dados. Para isto existem registradores especiais, como
o registrador de deslocamento (shift-register) e o registrador contador (counter). Eles podem
ser facilmente implementados com flip-flops. Para isto basta interligar-se adequadamente as
entradas e saídas dos flip-flops, ou no máximo adicionar-se alguma lógica combinacional na
entrada dos flip-flops.

A Figura 9.20 ilustra um registrador de deslocamento que a cada ativação do sinal de


controle desloca todos os bits de uma posição para a direita. O novo valor do bit mais
significativo é fornecido pela entrada E. Este tipo de registrador também é conhecido como
um registrador de entrada e saída seriais. Um registrador de deslocamento para a esquerda
seria construído de maneira análoga a da Figura 9.20, basta ligar a saída de cada flip-flop na
entrada do flip-flop imediatamente à esquerda.
E

Flip-flop Flip-flop ....... Flip-flop Flip-flop


D D D D

C S

Figura 9.20 - Registrador de n bits de carga e saída seriais

Também é extremamente fácil fazer um registrador de deslocamento de entrada serial e saída


paralela; basta disponibilizar as saídas de todos os flip-flops internos do registrador. Para um
registrador de deslocamento com entrada paralela, é necessário acrescentar um multiplexador
na entrada de cada flip-flop, que selecionaria entre o dado a ser deslocado e o dado a ser
carregado no registrador.
Um registrador contador, ou simplesmente contador, é um registrador que, com a ativação
do sinal de controle, incrementa (ou decrementa) o seu valor de uma unidade. Dependendo
do tipo de contagem desejada (binária, BCD, etc) o contador apresenta uma estrutura interna
particular. Além disto, o contador pode ser projetado de tal forma que, ao atingir o valor n-1,
ele volta a zero no controle seguinte. Neste caso, diz-se que o contador é módulo n, ou seja,
ele divide por n.

9-14
Para contadores é útil o uso de flip-flop sensíveis à borda, para que a contagem se realize em
um instante de tempo bem preciso. A Figura 9.21 ilustra um contador binário de n bits
utilizando flip-flops tipo T. A contagem binária é obtida conectando-se a saída de um bit na
entrada do flip-flop seguinte. Observe-se que, para facilitar o desenho, o bit menos
significativo está a esquerda e o mais significativo a direita.

Flip-flop Flip-flop ....... Flip-flop Flip-flop


T T T T

C
Q0 Q1 ....... Qn-2 Qn-1
Figura 9.21 - Contador binário de n bits

Se os flip-flops do contador da Figura 9.21 fossem sensíveis à borda de subida, teríamos um


contador decrescente. Sendo eles sensíveis à borda de descida, tem-se um contador
crescente. Assim como os registradores de deslocamento, os contadores também podem
apresentar entrada de dados paralela. Neste caso também é necessário adicionar-se na entrada
de cada flip-flop um multiplexador para selecionar entre a carga de dados e a contagem.
Observe-se que, para contadores complexos, os flip-flops tipo JK são os mais utilizados,
por apresentarem controle mais complexo e assim possibilitarem contagens mais complexas.

9 . 6 Unidade Aritmética e Lógica


Uma das partes essenciais de um computador é a Unidade Aritmética e Lógica, abreviada
comumente para UAL ou ULA. Esta unidade é responsável pela execução de somas,
subtrações, funções booleanas, comparações, etc. Sua complexidade é diretamente
proporcional à complexidade do conjunto de instruções do computador. Computadores
simples possuem ULAs simples, com cerca de 4 funções distintas. Computadores
complexos possuem ULAs complexas, com 8 a 16 funções distintas.
Operações booleanas podem ser implementadas com portas lógicas simples, do tipo AND,
OR e NOT. Além disto, elas não apresentam nenhuma interdependência entre os bits
vizinhos, o que permite que uma operação lógica sobre n bits seja implementada por n
operadores independentes.

Operações aritméticas, por outro lado, requerem uma implementação mais complexa. Um
somador binário simples (meio-somador), de um bit, soma dois operandos de um bit (A e B)
e produz um bit de resultado (S) e um bit de carry-out (C). O meio-somador possui a tabela-
verdade mostrada na Tabela 9.19, e pode ser implementado através de um ou-exclusivo (para
a soma) e uma porta AND (para o carry-out), como é mostrado na Figura 9.22.

A B S C
0 0 0 0
0 1 1 0
1 0 1 0
1 1 0 1
Tabela 9.19 - Tabela-verdade de um meio-somador

9-15
A
S = a⊕b
B S
A
Meio
Somador
B C
C = a.b

Figura 9.22 - Meio-somador

O somador completo possui uma terceira entrada (carry-in), que corresponde ao carry out do
bit menos significativo. Sua tabela-verdade pode ser vista na Tabela 9.20. A Figura 9.23
mostra a implementação em termos de portas lógicas, e uma implementação através de meio-
somadores pode ser vista na Figura 9.24.

A B Ci S Co
0 0 0 0 0
0 0 1 1 0
0 1 0 1 0
0 1 1 0 1
1 0 0 1 0
1 0 1 0 1
1 1 0 0 1
1 1 1 1 1
Tabela 9.20 - Tabela-verdade de um somador completo

A
A
B S = A⊕B⊕Ci

Ci

Co = AB+Ci(A⊕B)

Figura 9.23 - Somador completo implementado com portas lógicas

A C
Meio Co
Somador
B S C
Meio
Somador
Ci S S

Figura 9.24 - Somador completo implementado com meio-somadores

9-16
Um somador completo realiza a soma de um bit. Para somar n bits, é necessário agrupar n
somadores completos, onde o carry-out de cada um é transportado para o carry-in do
somador imediatamente à esquerda, conforme pode ser visto na Figura 9.25.

An-1 Bn-1 An-2 Bn-2 ....... A1 B1 A0 B0

Ci=0

Somador Somador ....... Somador Somador


completo completo completo completo

C
Sn-1 Sn-2 ....... S1 S0
Figura 9.25- Somador binário de n bits
O somador de n bits da Figura 9.25 é relativamente simples, mas apresenta um grande
problema: o grande atraso provocado pela propagação do carry entre os vários somadores. O
somador de índice j deve esperar que todos os j somadores anteriores (j= 0,1,... i-1)
terminem de calcular os bits de soma de carry-out, antes de que possa considerar o sinal de
carry-in como válido. Com n somadores, o tempo de propagação é n vezes maior do que o
tempo de um somador de um bit. Esta interconexão entre somadores recebe o nome de ripple
carry. Somadores mais complexos diminuem este atraso usando técnicas de carry look-
ahead.

A operação de subtração pode ser efetuada implementando-se o circuito de um subtrator


completo, ou então utilizando-se a técnica de complemento do subtraendo (a-b = a + not(b) +
1).
A B

Ci=0
Somador

not(A)
A+B A and B A or B

2
Sel

Figura 9.26 - ULA com 4 operações

9-17
Se uma ULA realiza diversas funções, uma forma simples de realizar sua implementação é
projetar individualmente cada uma destas funções, e depois simplesmente reuni-las através
de um multiplexador, que seleciona qual o valor a ser apresentado na saída. Por exemplo,
suponha-se que uma ULA deva realizar as operações de ADD, AND, OR e NOT. A
implementação desta ULA com um multiplexador de 4-para-1 pode ser vista na Figura 9.26.
Todas as linhas, com exceção de Ci e Sel, são de n bits.
Assim como qualquer outro circuito, também para a ULA vale o compromisso entre
desempenho e custo. Embora a ULA da Figura 9.26 tenha baixo custo, pois só utiliza
elementos padrões, não tem alto desempenho, pois não foi otimizada para isto.

9 . 7 Memória

Assim como um registrador de n bits pode ser visto como um array de n flip-flops, uma
memória de m posições pode ser vista como um array de m registradores. Logicamente, o
funcionamento de uma memória pode ser visto na figura 1.27. Observe-se, entretanto, que
este é um modelo lógico, e não um modelo da estrutura física da memória. Na operação de
escrita as linhas de endereço selecionam, através de um circuito decodificador, em qual
registrador o dado deve ser escrito. A saída de decodificador, juntamente com o sinal de
Write, formam o sinal de carga nos registradores. Na operação de leitura as mesmas linhas
de endereço selecionam, através de um multiplexador, qual o registrador que terá o seu
conteúdo levado até a saída. A habilitação do dado na saída é realizada pelo sinal Read.
A implementação dos circuitos de memória atuais é diferente do modelo apresentado na
Figura 9.27. Este modelo é muito simplificado, e visa somente explicar o funcionamento
geral da memória. Observe-se inclusive que os tempos necessários para a realização de uma
operação de escrita ou leitura não estão modelados na figura.

Endereço Write Dado de Entrada Leitura


2
8

Posição 0
carga
8
8

Posição 1
carga Dado de
8 Saída
2
8 8

Posição 2
carga
8
8

Posição 3
carga
8

Figura 9.27 - Estrutura lógica de uma memória de 4 posições

9-18
Capítulo
DEZ
Organização do Neander

1 0 . 1 Elementos necessários

Para definir uma organização para o computador NEANDER, é necessário inicialmente


definir quais os elementos necessários. Estes dados podem ser retirados das próprias
características do NEANDER:
• Largura de dados e endereços de 8 bits
• Dados representados em complemento de dois
• 1 acumulador de 8 bits (AC)
• 1 apontador de programa de 8 bits (PC)
• 1 registrador de estado com 2 códigos de condição: negativo (N) e zero (Z)
Assim, os seguintes elementos são necessários:
• Um registrador de 8 bits para o acumulador
• Um registrador de 8 bits para o PC (e possivelmente um registrador contador)
• Dois flip-flops: um para o código de condição N e outro para Z
• Uma memória de 256 posições de 8 bits cada

1 0 . 2 Fluxo de dados
O conjunto de instruções do NEANDER fornece mais detalhes sobre o uso e as
interconexões necessárias entre estes elementos:
Código Instrução Execução
0000 NOP nenhuma operação
0001 STA end MEM(end) ← AC
0010 LDA end AC← MEM(end)
0011 ADD end AC← MEM(end) + AC
0100 OR end AC← MEM(end) OR AC
0101 AND end AC← MEM(end) AND AC
0110 NOT AC← NOT AC
1000 JMP end PC← end
1001 JN end IF N=1 THEN PC ← end
1010 JZ end IF Z=1 THEN PC ← end
1111 HLT término de execução - (halt)
Tabela 10.1 - Conjunto de instruções do NEANDER

A fase de busca de cada instrução não está mostrada na Tabela 10.1, mas é igual para todas
as instruções:
RI ← MEM(PC)
PC ← PC + 1
Com isto introduz-se um novo elemento, o Registrador de Instruções (RI), que deve
apresentar tamanho suficiente para armazenar uma instrução completa.

10-1
A seguir estão descritas as transferências entre os diversos elementos de armazenamento que
formam a organização do NEANDER. Note-se que estas transferências já indicam quais os
caminhos de dados necessários (qual saída de um registrador deve ser levada até qual entrada
de outro registrador), mas não indicam qual o caminho físico exato utilizado para a
transferência.

Instrução NOP
Busca: RI ← MEM(PC)
PC ← PC +1
Execução: Nenhuma operação necessária
Instrução STA
Busca: RI ← MEM(PC)
PC ← PC +1
Execução: end ← MEM(PC)
PC ← PC +1
MEM(end) ← AC

Instrução LDA
Busca: RI ← MEM(PC)
PC ← PC +1
Execução: end ← MEM(PC)
PC ← PC +1
AC ← MEM(end); atualiza N e Z
Instrução ADD
Busca: RI ← MEM(PC)
PC ← PC +1
Execução: end ← MEM(PC)
PC ← PC +1
AC ← AC + MEM(end); atualiza N e Z
Instrução OR
Busca: RI ← MEM(PC)
PC ← PC +1
Execução: end ← MEM(PC)
PC ← PC +1
AC ← AC or MEM(end); atualiza N e Z
Instrução AND
Busca: RI ← MEM(PC)
PC ← PC +1
Execução: end ← MEM(PC)
PC ← PC +1
AC ← AC and MEM(end); atualiza N e Z

Instrução NOT
Busca: RI ← MEM(PC)
PC ← PC +1
Execução: AC ← NOT(AC); atualiza N e Z
Instrução JMP
Busca: RI ← MEM(PC)
PC ← PC +1
Execução: end ← MEM(PC)
PC ← end

10-2
Instrução JN, caso em que N=1
Busca: RI ← MEM(PC)
PC ← PC +1
Execução: end ← MEM(PC)
PC ← end
Instrução JN, caso em que N=0
Busca: RI ← MEM(PC)
PC ← PC +1
Execução: end ← MEM(PC) (esta transferência a rigor é desnecessária)
PC ← PC + 1

Instrução JZ, caso em que Z=1


Busca: RI ← MEM(PC)
PC ← PC +1
Execução: end ← MEM(PC)
PC ← end

Instrução JZ, caso em que Z=0


Busca: RI ← MEM(PC)
PC ← PC +1
Execução: end ← MEM(PC) (esta transferência a rigor é desnecessária)
PC ← PC + 1

Instrução HLT
Busca: RI ← MEM(PC)
PC ← PC +1
Execução: Parar o processamento
Uma transferência do tipo x ← MEM(y) descreve uma leitura de memória. Esta operação
pode ser decomposta em três fases:
1. REM ← y Transferência do endereço y para o Reg. de Endereços da Memória
2. Read Ativação de uma operação de Leitura da Memória
3. x ← RDM Transferência do Reg. de Dados da Memória para x
Por outro lado, uma transferência do tipo MEM(y) ← x descreve uma escrita de memória.
Esta operação também pode ser decomposta em três fases:
1. REM ← y Transferência do endereço y para o Reg. de Endereços da Memória
2. RDM ← x Transferência do dado x para o Reg. de Dados da Memória
3. Write Ativação da operação de Escrita na Memória
Além disto, as seguintes observações podem ser feitas:
1. Após uma leitura de memória na posição do PC, o conteúdo deste registrador deve
ser incrementado, para apontar para a posição seguinte. Esta operação pode ser feita
a qualquer instante de tempo após a transferência do PC para o REM. E o
incremento pode ser feito em paralelo com outras operações. Nas seqüências
descritas a seguir, este incremento é feito sempre ao mesmo tempo que a operação
na memória (Read ou Write).
2. Um desvio condicional que não se realize não necessita ler o valor do endereço de
desvio. Assim, basta incrementar mais uma vez o valor do PC, para que este “pule”
sobre a posição de memória que contém este endereço e passe a apontar para a
instrução seguinte.
Com isto, obtêm-se as seguintes seqüências:
Instrução NOP:
Busca: REM ← PC
Read; PC ← PC + 1
RI ← RDM

10-3
Execução: Nenhuma operação
Instrução STA
Busca: REM ← PC
Read; PC ← PC + 1
RI ← RDM
Execução: REM ← PC
Read; PC ← PC +1
REM ← RDM
RDM ← AC
Write
Instrução LDA
Busca: REM ← PC
Read; PC ← PC + 1
RI ← RDM
Execução: REM ← PC
Read; PC ← PC +1
REM ← RDM
Read
AC ← RDM; Atualiza N e Z

Instrução ADD
Busca: REM ← PC
Read; PC ← PC + 1
RI ← RDM
Execução: REM ← PC
Read; PC ← PC +1
REM ← RDM
Read
AC ← AC + RDM; Atualiza N e Z

Instrução OR
Busca: REM ← PC
Read; PC ← PC + 1
RI ← RDM
Execução: REM ← PC
Read; PC ← PC +1
REM ← RDM
Read
AC ← AC or RDM; Atualiza N e Z

Instrução AND
Busca: REM ← PC
Read; PC ← PC + 1
RI ← RDM
Execução: REM ← PC
Read; PC ← PC +1
REM ← RDM
Read
AC ← AC and RDM; Atualiza N e Z

Instrução NOT
Busca: REM ← PC
Read; PC ← PC + 1
RI ← RDM
Execução: AC ← not(AC); Atualiza N e Z

10-4
Instrução JMP
Busca: REM ← PC
Read; PC ← PC + 1
RI ← RDM
Execução: REM ← PC
Read
PC ← RDM

Instrução JN quando N=1


Busca: REM ← PC
Read; PC ← PC + 1
RI ← RDM
Execução: REM ← PC
Read
PC ← RDM

Instrução JN quando N=0


Busca: REM ← PC
Read; PC ← PC + 1
RI ← RDM
Execução: PC ← PC + 1

Instrução JZ quando Z=1


Busca: REM ← PC
Read; PC ← PC + 1
RI ← RDM
Execução: REM ← PC
Read
PC ← RDM

Instrução JZ quando Z=0


Busca: REM ← PC
Read; PC ← PC + 1
RI ← RDM
Execução: PC ← PC + 1

Instrução HLT
Busca: REM ← PC
Read; PC ← PC + 1
RI ← RDM
Execução: Parar o processamento
A Figura 10.1 ilustra uma possível interconexão entre os elementos de armazenamento e os
elementos combinacionais necessários para implementar a arquitetura do NEANDER. Ela é
derivada quase que diretamente do fluxo de dados mostrado acima.

Para a organização do NEANDER mostrada na Figura 10.1 as seguintes considerações


podem ser feitas:
1. O incremento do PC poderia ser feito de várias maneiras. Entre elas, podem ser
citadas a soma feita através da UAL, a soma feita através de um somador próprio e
o uso de um registrador contador. Para a organização optou-se por esta última.

2. Para cada registrador é necessário um sinal de “carga” correspondente, que indica


quando o valor da sua entrada deve ser armazenado.

10-5
carga RDM
write
read

Operações da UAL

X and Y
X or Y
not X
X+Y
MEM

RDM

Y
carga REM

M
R
E

carga RI
s1

M
P
X

RI
don't care

Sinais de Controle para a UCP


carga PC

Unidade de Controle
Cód. Op.

DECOD.
P
C
Incrementa PC

N Z

carga NZ
Y

UAL
X

seleção UAL
carga AC

AC

Figura 10.1 - Organização para o NEANDER

3. Para atualizar os códigos de N e Z durante a operação de LDA, acrescentou-se uma


operação de transferência através da UAL. Com isto, a UAL realiza cinco operações
possíveis.

4. As entradas X e Y da UAL, assim como as operações de NOT(X) e Y, foram


escolhidas de forma a simplificar as transferências. Com isto, a entrada X está
permanentemente ligada à saída da UAL, e a entrada Y da UAL está ligada ao
RDM.

10-6
5. O único registrador que recebe dados de duas fontes possíveis é o REM. Para
solucionar este conflito utiliza-se um multiplexador que seleciona entre o PC (sel=0)
e o RDM (sel=1).

1 0 . 3 Sinais de controle
Todos os sinais de controle da Figura 10.1 são gerados nos instantes de tempo adequados
pela Unidade de Controle. A Tabela 10.2 mostra a equivalência entre as transferências
realizadas e os sinais de controle a serem ativados.

Transferência Sinais de controle


REM ← PC sel=0, carga REM
PC ← PC + 1 incrementa PC
RI ← RDM carga RI
REM ← RDM sel =1, carga REM
RDM ← AC carga RDM
AC ← RDM; Atualiza N e Z UAL(Y), carga AC, carga NZ
AC ← AC + RDM; Atualiza N e Z UAL(ADD), carga AC, carga NZ
AC ← AC or RDM; Atualiza N e Z UAL(OR), carga AC, carga NZ
AC ← AC and RDM; Atualiza N e Z UAL(AND), carga AC, carga NZ
AC ← not(AC); Atualiza N e Z UAL(NOT), carga AC, carga NZ
PC ← RDM carga PC
Tabela 10.2 - Sinais de controle para as transferências do NEANDER

Com isto, as seqüências de transferências entre registradores analisadas anteriormente para


cada instrução podem ser transformadas em seqüências de sinais de controle. A Tabela 10.3
mostra estas seqüências para as instruções STA, LDA, ADD, OR, AND e NOT. A Tabela
10.4 mostra as seqüências para as instruções NOP, JMP, JN, JZ e HLT

tempo STA LDA ADD OR AND NOT


t0 sel=0, sel=0, sel=0, sel=0, sel=0, sel=0,
carga REM carga REM carga REM carga REM carga REM carga REM
t1 Read, Read, Read, Read, Read, Read,
incrementa incrementa incrementa incrementa incrementa incrementa
PC PC PC PC PC PC
t2 carga RI carga RI carga RI carga RI carga RI carga RI
t3 sel=0, sel=0, sel=0, sel=0, sel=0, UAL(NOT),
carga REM carga REM carga REM carga REM carga REM carga AC,
carga NZ,
goto t0
t4 Read, Read, Read, Read, Read,
incrementa incrementa incrementa incrementa incrementa
PC PC PC PC PC
t5 sel=1, sel=1, sel=1, sel=1, sel=1,
carga REM carga REM carga REM carga REM carga REM
t6 carga RDM Read Read Read Read
t7 Write, UAL(Y), UAL(ADD), UAL(OR), UAL(AND,
goto t0 carga AC, carga AC, carga AC, carga AC,
carga NZ, carga NZ, carga NZ, carga NZ,
goto t0 goto t0 goto t0 goto t0
Tabela 10.3- Sinais de controle para STA, LDA, ADD, OR, AND e NOT

10-7
tempo JMP JN, N=1 JN, N=0 JZ, Z=1 JZ, Z=0 NOP HLT
t0 sel=0, sel=0, sel=0, sel=0, sel=0, sel=0, sel=0,
carga REM carga REM carga REM carga REM carga REM carga REM carga REM
t1 Read, Read, Read, Read, Read, Read, Read,
incrementa incrementa incrementa incrementa incrementa incrementa incrementa
PC PC PC PC PC PC PC
t2 carga RI carga RI carga RI carga RI carga RI carga RI carga RI
t3 sel=0, sel=0, incrementa sel=0, incrementa goto t0 Halt
carga REM carga REM PC, carga REM PC,
goto t0 goto t0
t4 Read Read Read

t5 carga PC, carga PC, carga PC,


goto t0 goto t0 goto t0
t6
t7
Tabela 10.4- Sinais de controle para JMP, JN, JZ, NOP e HLT

Para o sequenciamento de todas as instruções são necessários 8 tempos distintos, numerados


de t0 a t7. Os três primeiros, t0, t1 e t2, servem para a fase de busca das instruções. Os
demais (t3 a t7) são necessários para a fase de execução. Observe-se que, quando termina a
execução de uma instrução, existe um sinal de controle explícito para voltar ao tempo t0
(goto t0). Das Tabelas 10.3 e 10.4 podem ser derivadas as equações booleanas para cada um
dos sinais de controle. Estas equações são indicadas a seguir, em forma não necessariamente
otimizada:
carga REM = t0 + t3.(STA+LDA+ADD+OR+AND+JMP+JN.N+JZ.Z +
t5.(STA+LDA+ADD+OR+AND)
incrementa PC = t1 + t4.(STA+LDA+ADD+OR+AND) + t3.(JN.N’ + JZ.Z’)
carga RI = t2
sel = t5.(STA+LDA+ADD+OR+AND)
carga RDM = t6.STA
Read = t1 + t4.(STA+LDA+ADD+OR+AND+JMP+JN.N+JZ.Z) +
t6.(LDA+ADD+OR+AND)
Write = t7.STA
UAL(Y) = t7.LDA
UAL(ADD) = t7.ADD
UAL(OR) = t7.OR
UAL(AND) = t7.AND
UAL(NOT) = t3.NOT
carga AC = t7.(LDA+ADD+OR+AND) + t3.NOT
carga NZ = t7.(LDA+ADD+OR+AND) + t3.NOT = carga AC
carga PC = t5.(JMP+JN.N+JZ.Z)
goto t0 = t7.(STA+LDA+ADD+OR+AND) + t3.(NOP+NOT+JN.N’+JZ.Z’) +
t5.(JMP+JN.N+JZ.Z)

10-8
Capítulo
ONZE
Noções de Entrada e Saída

1 1 . 1 Introdução

Englobam-se sob a denominação de Entrada e Saída (E/S) todas as atividades de troca de


informações do computador com o meio externo. Basicamente, o computador pode utilizar
atividades de entrada/saída com as seguintes finalidades:

• troca de informações com os usuários humanos do sistema, utilizando-se, para a


entrada de informações ou para a exibição, dos chamados dispositivos periféricos.
Estes dispositivos podem ser teclados, vídeos, mouses, impressoras, leitoras de
cartões, etc.
• permitir ao computador acessar dispositivos de armazenamento para grandes conjuntos
de dados, e que normalmente não podem ser mantidos todo o tempo em memória
primária. Estes dispositivos são englobados sob a denominação de memória
secundária e incluem, em geral discos e disquetes magnéticos, CD-Rom, discos
magneto-óticos e fitas magnéticas.
• permitir ao computador comunicar-se com outros equipamentos, tais como outros
computadores e outros equipamentos científicos e industriais, através de linhas
telefônicas e equipamentos específicos requeridos por este tipo de comunicação
(modens).
A arquitetura de E/S os elementos que um programa utiliza para transportar dados entre o
processador e os diversos dispositivos de E/S. Ela precisa especificar:

• um método para identificação do dispositivo a ser utilizado em uma operação de E/S;


• o endereço do dado, ou seja, a fonte do dado a ser transportado para uma operação de
saída ou o destino do dado em uma operação de entrada;
• a quantidade de dados a serem transportados;
• os métodos para determinar quando as operações de E/S tiverem terminado ou
encontrarem dificuldades que impeçam o seu término.
Estes parâmetros não precisam ser necessariamente especificados a cada transferência; alguns
deles podem ser convencionados de tal forma que estarão implícitos quando ocorrer o início
da operação.

Uma característica importante e fundamental da maioria dos dispositivos de E/S, sejam eles
orientados ao intercâmbio de informações diretamente com o usuário ou atuando no
armazenamento intermediário de informações, é que eles operam em velocidades
consideravelmente mais lentas do que em processadores ou memórias (do tipo “primárias”).
Assim, referências a dados ou interações com seres humanos consomem enormes
quantidades de tempo em comparação a funções computacionais dentro de um par
processador-memória. Por esta razão, o projeto de E/S é crucial para minimizar atrasos e o
projeto de funções de E/S é normalmente sentido diretamente na arquitetura.

11-1
1 1 . 2 Dispositivos periféricos
A classe de dispositivos que são utilizados atualmente para realização de operações de
entrada e saída (isto é, que atuam na troca de informações tendo de um lado um computador
e do outro usuários humanos do sistema) é bastante ampla e diversa, apresentando um leque
bastante variado de características e operação. Entre elas estão:
• impressoras: para criar cópia em papel ou similar, de textos e figuras. As impressoras
variam em atributos, tendo com forma mais primitiva dispositivos que se
assemelham a máquinas de escrever. Atualmente, as mais modernas utilizam como
tecnologia a impressão a laser e podem imprimir até milhares de linha por minuto;
• monitores (ou telas): que mostram textos e figuras, ambos podendo ser formatados de
diversas maneiras e utilizar várias cores. O tipo mais comum, utiliza um tubo não
muito diferente de um tubo de televisão (tubo de raios catódicos). Atualmente,
utiliza-se também visores de cristal líquido, que consomem menor quantidade de
energia;
• dispositivos gráficos (ou plotters): que criam cópias impressas de gráficos e curvas de
papel. Com o avanço tecnológico, as impressoras atualmente tem capacidade de
produzir material gráfico de ótima qualidade, tendo sua maior limitação no campo
(tamanho) de impressão. Os traçadores, dependendo das suas características podem
produzir desenhos com mais de um metro de largura e comprimento limitado pela
bobina de papel;
• dispositivos de exploração ótica: que podem ler diretamente documentos. Dentre os
mais primitivos, pode-se citar as leitoras óticas de cartões ou folhas de marcas
(como as usadas no vestibular para registro das respostas). Atualmente, existem
dispositivos bem mais complexos como os scanners, que podem transferir para o
computador imagens obtidas em figuras, textos e fotografias;
• dispositivos de apontamento: que podem indicar informações ao computador através do
posicionamento de um cursor sobre a tela, como é o caso do mouse e da caneta
luminosa (ou lightpen).

O âmbito de dispositivos utilizados por computadores para comunicar-se com os usuários


encontra-se em expansão e melhorando em qualidade de interação. De forma a complementar
os dispositivos recém-mencionados, há grandes avanços no campo do reconhecimento de
voz e saída de voz. Já há alguma disponibilidade nesta última opção.

1 1 . 3 Memória secundária

O grande volume de dados armazenados no computador inviabiliza a manutenção permanente


destas informações integralmente na memória principal. Isto faz com que grande parte das
informações, utilizadas menos freqüentemente, seja guardada em dispositivos de uma classe
diferente dos empregados para a construção da memória principal (antes, memórias de
núcleos magnéticos, atualmente circuitos semicondutores). Os dispositivos mais usados
anteriormente eram discos, tambores rotativos e fitas, todos magnéticos; atualmente são os
discos e disquetes magnéticos, fitas magnéticas e os CD's.

Estes dispositivos são endereçados utilizando-se conceitos de arquitetura e organização de


E/S, embora não se enquadrem na primeira classe apresentada de dispositivos de E/S que
pressupõem interação entre o computador e o usuário ou ser humano. Eles podem ser
considerados como arquivos eletrônicos que estão aptos a responder a uma solicitação de
dados.

1 1 . 4 Comunicação com outras máquinas

A troca de informações com outras máquinas também consiste em uma atividade de entrada e
saída. Inicialmente, eram utilizadas para troca de informações entre computadores ou entre
computador e algum periférico especial localizados dentro de um prédio, em salas próximas.

11-2
Atualmente são usadas para comunicação entre máquinas situadas geograficamente distantes,
empregando linhas telefônicas e outros meios convencionais. Para isto, os sinais
empregados devem seguir normas especiais, afim de não perturbar os serviços normais
oferecidos pela rede telefônica. Estes serviços são utilizados em larga escala como, por
exemplo: por bancos, para transferir informações referentes às transações, saldos, extratos,
etc; por universidades e centros de pesquisa, para difusão de conhecimento e troca de
informações; por empresas, para difusão de informações entre filiais e setores agregados ou
junto a clientes, etc.

As características básicas desta atividade diferem das demais, principalmente devido à


distância onde os elementos envolvidos estão colocados. Portanto, neste tipo de operação de
E/S:
• existem equipamentos que operam eletricamente os dados para controlar nível de
sinal, forma de transmissão, etc, que são os chamados “modens” (moduladores-
demoduladores);
• a informação está mais sujeita a ser modificada, com a introdução de erros, por efeito
de ruído elétrico. Assim, em geral há necessidade de utilizar formas especiais de
codificação;
• para evitar que haja muita perda de tempo no estabelecimento da comunicação,
incluindo o envio de informações de controle, convém trabalhar com a transmissão de
blocos de informação grandes. Isto faz com que, em cada bloco de informação
transmitida, haja um percentual pequeno de caracteres ou palavras destinadas ao
controle e exigidas pelo protocolo de comunicação.

1 1 . 5 Sistemas de E/S
Como visto nas unidades anteriores, as atividades principais dos computadores envolvem
dois componentes: a unidade central de processamento (UCP) e a memória principal. A
unidade central de processamento busca instruções e dados na memória principal, processa-
os e armazena os resultados na memória principal. Os demais componentes podem ser
genericamente denominados de sistema de entrada e saída, uma vez que destinam-se a
transferir informações entre a memória ou a CPU e o ambiente externo a estes. O sistema de
E/S inclui os dispositivos de E/S (periféricos ou demais elementos), unidades de controle
para estes dispositivos e o software especialmente projetado para operações de E/S. Na
seqüência, serão referidos apenas periféricos, já que as demais operações de entrada e saída
podem ser tratadas de forma análoga.

Existem várias maneiras de se executar as operações de E/S: a possibilidade de se empregar


estas formas depende dos recursos de software e hardware disponíveis. A opção por uma
delas depende basicamente do desempenho desejado, dos dispositivos envolvidos, e do
volume dos dados.

1 1 . 5 . 1 Entrada e saída programada


Quando as operações de E/S são controladas inteiramente pela UCP, isto é, a UCP executa
programas que iniciam, dirigem e terminam as operações de E/S, diz-se que o computador
emprega E/S programada. Este sistema existe em quase todos os computadores: emprega
pouco hardware especial, mas pode ocupar tempo demasiado da UCP no controle de
operações relativamente simples de E/S. Uma destas funções é o teste do estado dos
dispositivos de E/S para determinar se eles estão ou não requisitando serviços da CPU.

Neste contexto, a transferência é realizada, em geral, entre um registrador da UCP (por


exemplo, o acumulador) e um registrador ligado ao dispositivo de E/S. A transferência de
um dado do dispositivo de E/S para a memória principal necessita da execução de duas
instruções pela UCP:

11-3
• uma instrução (INPUT) para transferir uma palavra de um dispositivo de E/S para a
UCP;
• uma instrução (STORE) para transferir uma palavra da UCP para a memória principal.

Endereçamento

Em sistemas com E/S programada, os dispositivos de E/S, a memória principal e a UCP se


comunicam através de um barramento de uso comum, partilhado entre eles. As linhas de
endereçamento deste barramento, usadas para para selecionar posições de memória, também
podem ser utilizadas para selecionar dispositivos de E/S. Cada ligação entre o barramento
principal e o dispositivo de E/S é chamada de porta de E/S, sendo atribuída a ela um
endereço exclusivo. A porta de E/S pode incluir um registrador de dados.
Em algumas máquinas, parte do espaço de endereçamento da memória principal é usado para
portas de E/S (E/S mapeada em memória). Neste caso, não há necessidade de dispor-se de
instruções especiais de entrada e saída, mas estes aspectos serão tratados mais tarde, em
outras disciplinas.

Instruções de E/S

O esquema de E/S programada pode ser implementado com poucas instruções. O


microprocessador Intel 8080, por exemplo, que usa o sistema de endereçamento explicado
no parágrafo anterior, emprega apenas duas instruções de E/S:
• IN x, que transfere uma palavra da porta de E/S x para o acumulador do 8080;
• OUT x, que transfere uma palavra do acumulador do 8080 para a porta de E/S x.
A UCP não atribui qualquer interpretação às palavras transferidas: o programador deve fazê-
lo. Algumas palavras podem indicar o estado do dispositivo de E/S e outras podem ser
instruções especiais para este dispositivo.

O simples envio de dados, sem que o dispositivo esteja pronto para recebê-los, pode resultar
na perda de informações. Por outro lado, a espera de informações sem que haja tempos pré-
determinados, pode resultar em tempos de execução excessivamente longos. Assim, o ideal é
que se tenha possibilidade de testar as condições de recebimento / envio de informações pelo
dispositivo de E/S, ou seja, avaliar o estado do dispositivo. Normalmente este estado pode
ser especificado por um bit de informação que o dispositivo mantém disponível de forma
contínua (independente das linhas de dados). Assim, serão necessários os seguintes passos:
• Passo 1: lê a informação de estado;
• Passo 2: testa o estado para determinar se o dispositivo está pronto para iniciar a
transferência de dados;
• Passo 3: se não está pronto, retorna ao passo 1; se está pronto, efetua a transferência
do dado.
A seguir, tem-se como exemplo um programa escrito para o INTEL 8080, que executa a
transferência de uma palavra de dados de um dispositivo de E/S para o acumulador na UCP.
Por hipótese, o dispositivo está ligado às portas 1 e 2. O estado do dispositivo está
disponível continuamente na porta 1, enquanto que os dados solicitados estão disponíveis na
porta 2 quando a palavra de estado tem o valor READY.

Instrução Comentário
WAIT: IN 1 Lê estado do dispositivo de E/s para o acumulador
CMP READY Compara a palavra READY (de forma imediata, com o
acumulador; de for igual, liga Z=1, senão, Z=0
JNZ WAIT Se Z≠1 (disp. de E/S não está pronto), volta para WAIT
IN 2 Lê palavra de dados para o acumulador
Figura 11.1 - Trecho de programa de E/S

11-4
1 1 . 5 . 2 Acesso direto à memória
Com pequeno aumento na complexidade do hardware, o dispositivo de entrada e saída pode
ser munido da capacidade de transferir um bloco de informação da memória principal, ou
para esta, sem a intervenção da CPU. Isto requer que o dispositivo de E/S seja capaz de
gerar endereços e transferir dados através do barramento da memória principal. Também
deve existir um sinal de solicitação de acesso ao barramento e um mecanismo de seleção pré-
definido (por exemplo, por prioridades).

A UCP é responsável pela inicialização de cada transferência de cada bloco. A partir daí, o
dispositivo de E/S pode realizar a transferência sem a necessidade de execução de programa
pela UCP, ou seja, sem que a UCP se envolva diretamente na operação, exceto no seu final.
Quando isto ocorre, UCP e o dispositivo de E/S interagem e a UCP retoma o controle do
barramento através do qual foi feita a transferência. Este tipo de capacidade de entrada e saída
é denominada de acesso direto à memória.

1 1 . 5 . 3 Interrupção
O dispositivo de E/S também pode conter circuitos que o habilitam a requisitar serviços da
UCP, isto é, provocam na UCP a execução de um programa de atendimento ao dispositivo
periférico. Este tipo de solicitação é chamada de interrupção. A disponibilidade de serviços
de interrupção livra a UCP de estar periodicamente testando o estado do dispositivo de E/S
(afim de verificar se ele tem dados para transferir ou não). Diferentemente do acesso direto à
memória, uma interrupção faz com que a UCP páre temporariamente a execução do
programa em curso, salvando o estado correspondente, e transfira o controle de execução
para um programa de tratamento de interrupção. Assim, quando termina o atendimento da
interrupção, a UCP pode reassumir a execução do programa antes interrompido.

1 1 . 5 . 4 Informações complementares

Uma outra opção para execução das atividades de entrada e saída é a existência de
processadores especiais, denominados de processadores de E/S ou canais de E/S, que
assumem o controle total deste tipo de operações. Com isto, eles aliviam consideravelmente a
atividade da UCP, liberando-a para outro tipo de processamento. Estes processadores podem
acessar diretamente à memória, podem interromper a UCP, ou executar programas, com
conjuntos especiais de instruções, orientadas a operações de E/S. Eles também podem estar
ligados à UCP por um barramento especial, chamado de barramento de E/S ou interface de
E/S.

11-5
Capítulo
DOZE
Software Básico

1 2 . 1 Introdução

Emprega-se comumente a palavra software para designar o conjunto de programas que são
utilizados com um sistema de hardware para facilitar seu uso por programadores e
operadores do sistema. Entretanto, esta utilização do termo exclui programas de aplicações,
incluindo apenas programas que fornecem funções gerais independentes dos detalhes de uma
aplicação (software do sistema ou software básico). Na prática, o que o usuário enxerga
facilmente deste grupo (embora ele seja mais extenso) são as linguagens que ele encontra
disponíveis para programação, como Basic, Fortran, Cobol, Pascal, Logo, C, Delphi,
Visual C, C++, Java, etc, e programas escritos para resolver problemas específicos ou que
realizam funções especiais como planilhas de cálculo, editores de texto, jogos eletrônicos,
entre outros, que fazem parte do software de aplicação.
Os programas que são escritos em linguagens de programação de alto nível (emprega-se esta
denominação como distinção a linguagens mais próximas da máquina) precisam ser
convertidos em programas de máquina. O processo desta conversão é executado por um
elemento de software denominado de processador de linguagem. Os programas escritos
em alto nível tendem a ser independentes da estrutura da máquina na qual serão executados,
mas os programas de baixo nível não. Por isso, esta conversão é dependente da máquina
sobre a qual vai ser executado o programa.
Os processadores de linguagem são programas longos, que ocupam espaço significativo na
memória do computador. Por isso, freqüentemente ficam armazenados (residem) em um
dispositivo de entrada/saída, como um disco, por exemplo. Este programa será “chamado” e
copiado para a memória quando o programador quiser fazer uso dele. O uso de um
processador de linguagem significa que o programador vai executá-lo, tendo como entrada
de dados o programa escrito em linguagem de alto nível. Como saída, será obtida uma
representação do programa em forma diretamente executável pela máquina ou uma forma
mais próxima desta do que a representação anterior (em alto nível).

A escrita de um programa consiste na especificação, direta ou indireta, de uma seqüência de


instruções de máquina para o computador. As instruções de máquina formam um padrão
binário com o qual é difícil de trabalhar do ponto de vista humano e complexo para entender.
É mais fácil de escrever programas empregando símbolos mais familiares, orientados ao
usuário, tais como os caracteres alfa-numéricos e suas combinações. Como conseqüência
desta facilidade, torna-se necessário traduzir esta representação simbólica de programas em
elementos binários que possam ser reconhecidos pelo hardware.

1 2 . 2 Linguagens de programação

Um programa é uma lista de instruções ou comandos que determina ao computador a


execução de uma tarefa de processamento de dados. Há diversas linguagens de
programação, através das quais pode-se escrever programas para o computador; mas o
computador só pode executá-los quando eles estão representados internamente na forma
binária, organizado e codificado de forma a resultar na geração de sinais de controle da UCP.

12-1
Portanto, os programas que não estão escritos na forma binária necessitam ser traduzidos
antes da sua execução. Os programas podem se incluir em uma das seguintes categorias:

• código binário: sequência de instruções e operandos em binário; lista a


representação exata das instruções como elas aparecem na memória;
• código octal ou hexadecimal: representação, equivalente ao código binário, em
representação octal ou hexadecimal;

• código simbólico: o usuário emprega símbolos tais como letras, números e


caracteres especiais, para representar o código das instruções, composto por parte de
operação, endereço e outras. Cada instrução simbólica pode ser traduzida em uma
instrução binária equivalente, o que é feito por um programa montador (assembler).
Por esta razão, este programa simbólico é referido como programa em linguagem
de montagem ou assembler.
• linguagens de programação em alto nível: são linguagens especiais
desenvolvidas para facilitar a especificação de procedimentos usados na solução de
problemas, não havendo preocupação com o comportamento do hardware que suporta
estas operações. Portanto, elas empregam símbolos e formatos orientados a
problemas, de acordo com o raciocínio comumente empregado na solução destes.
Assim, cada comando precisa ser traduzido em uma seqüência de instruções binárias,
antes que o programa possa ser executado em um computador. O programa que traduz
um programa escrito em linguagem de alto-nível para binário é chamado de
compilador.
O termo linguagens de programação refere-se em geral ao estudo da estrutura das diversas
linguagens de programação de alto nível, sendo realizado de forma independente dos
dispositivos computacionais de do seu hardware. Este não é o enfoque para o pessoal de
arquitetura, que o utiliza com outra conotação incluindo linguagens a nível de máquina.
A seguir será exemplificada esta relação existente entre as diversas linguagens de
programação. Esta relação não é estabelecida diretamente a nível de comandos, mas tenta
deixar claro o nível crescente de dificuldades de programação e depuração de código, à
medida que de vai do alto para o baixo nível de programação.

1 2 . 3 Exemplo com NEANDER

Problema: Realizar a multiplicação de dois números inteiros positivos, a e b (a≥0, b≥0),


utilizando o método das somas sucessivas, ou seja, somar a consigo mesmo b vezes.

Descrição em português estruturado:

Sejam a,b,r três números inteiros positivos.


Então fazer:
1. Zerar r
2. Enquanto b for maior que zero, somar a em r e decrementar b de uma unidade
3. Fim. O resultado está em r

Descrição em pseudo-linguagem de alto nível


Begin Program
Variable a,b,r: type positive-integer
r:=0
while b>0
do r:=r+a
b:=b-1

12-2
end while
End Program

Descrição em pseudo-linguagem, sem uso de while

Begin Program
Variable a,b,r: type positive-integer
Label laço
r:=0
laço: if b>0
then r:=r+a
b:=b-1
goto laço
end if
End Program
Descrição em pseudo-linguagem, somente com uso de if-goto

Begin Program
Variable a,b,r: type positive-integer
Label laço, fim
r:=0
laço: if b=0 goto fim
r:=r+a
b:=b-1
goto laço
fim:
End Program
Descrição em pseudo-assembler

ORG 0 % define endereço de início do programa


LDA Zero % r:=0
STA r
laço: LDA b % if b=0 goto fim
JZ fim
LDA r % r:=r+a
ADD a
STA r
LDA b % b:=b-1
ADD m1
STA b
JMP laço % goto laço (poderia ser JMP laço+1: otimização !)
fim: HLT
a DEF BYTE % define variável a
b DEF BYTE % define variável b
r DEF BYTE % define variável r
zero DEF BYTE=0 % define variável zero e inicializa com 0
m1 DEF BYTE=-1 % define variável m1 e inicializa com menos um

Descrição na linguagem do NEANDER (sem labels)


Endereço Instrução
0 LDA 26 % r:=0
2 STA 25
4 LDA 24 % if b=0 goto fim

12-3
6 JN 22
8 LDA 25 % r:=r+a
10 ADD 23
12 STA 25
14 LDA 24 % b:=b-1
16 ADD 27
18 STA 24
20 JMP 4 % goto laço
22 HLT
23 0 % define variável a
24 0 % define variável b
25 0 % define variável r
26 0 % define variável zero e inicializa com 0
27 255 % define variável m1 e inicializa com menos um

Descrição na linguagem do NEANDER (em decimal)


Endereço Instrução
0 32 26 % r:=0
2 16 25
4 32 24 % if b=0 goto fim
6 160 22
8 32 25 % r:=r+a
10 48 23
12 16 25
14 32 24 % b:=b-1
16 48 27
18 16 24
20 128 4 % goto laço
22 240
23 0 % define variável a
24 0 % define variável b
25 0 % define variável r
26 0 % define variável zero e inicializa com 0
27 255 % define variável m1 e inicializa com menos um

1 2 . 4 Programas a nível de sistema

A produção de programas do sistema é complexa, e requer extenso conhecimento e prática


em computação. A existência destes programas representa vantagens e conveniência aos
programadores de aplicações e aos usuários de computadores em geral. O software do
sistema pode ser dividido em várias categorias (algumas citadas anteriormente):
• processadores de linguagens, que convertem programas para a linguagem de máquina
a partir de linguagens orientadas a usuários ou a aplicações;
• biblioteca de programas, que fornecem rotinas padrão para o programador de
aplicações;
• programas carregadores (ou “loaders”), para facilitar a carga dos diversos programas
na memória do computador;
• programas utilitários, para facilitar a comunicação entre os componentes do
computador e entre o computador e o usuário;
• programas de diagnóstico, que facilitam a manutenção do computador;
• um sistema operacional, que supervisiona os demais programas e controla sua
execução.

12-4
Assemblers
Nos primórdios da computação, o programador de computadores tinha a sua disposição uma
máquina que, através do hardware, executava certas funções básicas. A programação do
computador consistia em escrever uma série de uns e zeros (linguagem de máquina),
colocar esta série na memória, pressionar um botão. Com isto o computador iniciava a
execução destas instruções.
É extremamente difícil, entretanto, escrever e ler programas em linguagem de máquina. Na
procura por um método mais conveniente, desenvolveram-se os processadores de
linguagem, ou seja, programas que traduzem um programa fonte escrito pelo usuário
em um programa objeto que pode ser entendido pelo computador. Um montador ou
assembler é um programa do sistema que traduz um programa escrito em linguagem
assembler para um programa equivalente descrito em linguagem binária (da máquina).
Tipicamente, em uma linguagem assembler utilizam-se mnemônicos (símbolos) para cada
instrução de máquina, e a principal função do montador é traduzir cada um destes símbolos
no código binário equivalente.

Compiladores

Com o aumento da complexidade dos programas, mesmo o uso de linguagem assembler não
fornece o grau de abstração necessário para uma boa compreensão do programa. Assim,
desenvolveram-se as linguagens de alto nível, onde um único comando substitui dezenas de
instruções assembler. Um compilador é um programa do sistema que traduz um programa
em linguagem de alto-nível para a linguagem de máquina. Uma linguagem de alto nível é
suficientemente abstrata para ser independente do hardware (ou quase independente -
diversas características da arquitetura, como por exemplo a representação dos números, são
refletidos nas linguagens). Para poder utilizar uma linguagem de alto nível, o programador
deve conhecer sua sintaxe (forma) e sua semântica (significado).
As linguagens de alto nível estão em constante evolução, refletindo as mudanças que
ocorrem nas metodologias de programação. Dentre as primeiras linguagens destacam-se
FORTRAN, COBOL, ALGOL e PL/I. Entre as linguagens mais utilizadas atualmente podem
ser citadas C e PASCAL. Dois outros processadores de linguagens largamente usados são os
montadores de macros (ou macro-assemblers) e os interpretadores.
Macros
Uma macro é uma pseudo-instrução que define um grupo de instruções de máquina. Uma
pseudo-instrução é uma instrução que existe para definir condições especiais para o
montador; não resulta em código propriamente dito, mas provoca transformações no código
original. Um montador de macros traduz programas escritos em linguagem assembler
com facilidades de macros, o que significa usar nomes simbólicos para representar
seqüências de instruções. Cada vez que o programa encontra esta macro, substitui-a pela
seqüências de instruções.
Assim, por exemplo, poderia ser definida uma macro para o NEANDER que realizasse
subtrações. Tal macro (convenientemente denominada SUB), conteria as instruções
necessárias para realizar uma subtração (inversão do subtraendo, soma de uma unidade e
soma com o minuendo), como ilustrado na definição da macro a seguir:

MACRO SUB end % realiza AC <– AC – MEM(end)


STA aux % salva Acumulador em MEM(aux)
LDA end % note-se que end é um parâmetro da macro
NOT
ADD um % MEM(um) contém a constante UM
ADD aux
END MACRO

12-5
Todas as ocorrências posteriores do símbolo SUB seriam substituídas pela sequência acima,
uma vez para cada símbolo. As macros não devem ser confundidas com as subrotinas.
Subrotinas são conjuntos de instruções que podem ser utilizadas diversas vezes por um
programa; assim, ações repetitivas não precisam ser escritas repetidamente e nem geram
trechos repetidos de código em um programa. Cada vez que uma rotina é utilizada, é
executado um desvio do programa principal (é o que chama a subrotina) para o endereço
inicial onde ela se encontra carregada; após sua execução, é executado um novo desvio que
causa o retorno para o programa principal. Uma subrotina para subtração seria por exemplo
o seguinte trecho de programa, armazenado a partir do endereço xx :

xx STA aux % salva Acumulador em MEM(aux)


LDA end % note-se que end é um parâmetro da subrotina
NOT
ADD um % MEM(um) contém a constante UM
ADD aux
RET
A chamada desta função seria feita através de uma instrução de chamada para subrotina
(CALL xx) ou de desvio para subrotina (JSR xx). Note-se que na chamada deve ser
fornecido o endereço de memória onde a subrotina foi armazenada. Tanto a instrução de
chamada (CALL ou JSR) como a instrução de retorno (RET) devem existir na arquitetura do
computador. O NEANDER, por exemplo, não possui estas facilidades.

Interpretadores
Um interpretador também é um processador de linguagem que traduz cada comando de
um programa escrito em linguagem de alto nível, executando-o imediatamente. Assim, o
interpretador trabalha alternando ações de tradução e execução. O compilador atua
exclusivamente na tradução do programa fonte e não na execução, o que os diferencia.
Assim o interpretador responde mais rapidamente a modificações no programa fonte, o que é
bastante útil em um ambiente de desenvolvimento. Na execução rotineira de programas ele se
torna mais lento, uma vez que refaz a tradução de cada comando a cada nova execução. O
exemplo mais conhecido de interpretador é o utilizado na linguagem BASIC.

Bibliotecas
Bibliotecas de programas existem para simplificar tarefas repetitivas de programação.
Assim, rotinas muito utilizadas são padronizadas, catalogadas e tornadas acessíveis aos
usuários. Isto implica no estabelecimento de convenções. Nas aplicações científicas,
encontra-se neste campo a programação de funções matemáticas tais como raiz quadrada,
funções exponenciais, inversão de matrizes, etc Para processamento de dados comercial,
encontram-se funções de organização de arquivos como ordenação e busca ou procura.
Muitas outras estão disponíveis ou podem ser programadas de acordo com o interesse dos
usuários.

Carregadores

Carregadores são programas do sistema que inserem ou posicionam outros programas na


memória e preparam-nos para a execução. Podem ser do tipo absoluto, que carregam o
programa a partir de um endereço especificado pelo usuário, ou do tipo relocável, que não
permitem a intervenção do usuário, posicionando o programa de forma eficiente, de acordo
com as disponibilidades de espaço na memória.
Em um esquema simples, o montador assembler armazena sua saída em uma memória
secundária (disco, por exemplo). A função do carregador seria simplesmente transferir o
código desta memória secundária para a memória principal e transferir o controle para ele
(colocar no PC para o endereço da primeira instrução do programa).

12-6
O uso de generalizado de bibliotecas, subrotinas e macros, entretanto, faz com que um
carregador tenha mais tarefas a realizar. Basicamente, um carregador desempenha quatro
tarefas:

• Obter e reservar espaço de memória para o programa e suas subrotinas (Alocação).

• Resolver os endereços simbólicos (labels) entre o programa e as bibliotecas utilizadas


(Ligação ou linking ).

• Ajustar todas as referências a endereços, de forma que elas correspondam aos


endereços fisicamente utilizados, ou seja, aos endereços da porção da memória onde
o programa foi carregado (Relocação).
• Colocar fisicamente as instruções e os dados na memória (Carga).

O período de execução de um programa é denominado de tempo de execução. O período


de tradução de uma programa fonte é denominado de tempo de compilação ou tempo de
montagem. O tempo que se refere ao período de carga e preparação de um programa para
execução é chamado de tempo de carga.

Um programa carregador especial é o bootstrap loader, cuja função é inicializar as


atividades no computador, quando ele é ligado. O início da operação do computador não
ocorre apenas com a ligação da energia, mas com a execução de um programa de
inicialização. Este programa pode existir residente na memória ou pode ser carregado a cada
início de operação.

Utilitários
Programas utilitários correspondem a uma coleção de rotinas freqüentemente
empregadas que o programador pode usar para facilitar o seu trabalho de desenvolver tarefas
específicas e consequentemente seu trabalho de programação. Assim como no grupo
anterior, estão catalogados e disponíveis aos usuários do sistema. Como exemplos, pode-se
citar: editores de texto, ferramentas de depuração de programas (“memory dump”, “trace”,
“debuggers”, etc), rotinas de entrada e saída.
Programas de diagnóstico têm por objetivo exercitar certas partes do hardware do
sistema a fim de verificar situações de mau-funcionamento ou para testar a funcionalidade
destas unidades. Assim, ele fornece sinais ao hardware e coleta as respostas
correspondentes, comparando-as com as informações esperadas (resultados corretos obtidos
a partir da especificação da máquina, por exemplo). Estes programas auxiliam o pessoal que
trabalha em manutenção. Também podem ser rodados preventivamente para antecipar a
ocorrência de problemas.

Sistema Operacional

Um sistema operacional é uma coleção de programas que controlam a operação do


computador com o propósito de obter um desempenho eficiente. Consiste basicamente em
programas de controle permanentemente residentes na memória, que supervisionam todos
demais programas executados no computador. Em geral, sua eficiência é dependente dos
recursos de hardware existentes para suportá-lo, uma vez que ele faz uso intensivo destes
recursos. Sistemas operacionais rudimentares, instalados em geral em máquinas pequenas,
são referidos pelos termos: programa monitor, supervisor ou executivo. Os sistemas
operacionais serão estudados com maiores detalhes na próxima unidade.

1 2 . 5 Interface entre hardware e software

Uma questão essencial a nível da arquitetura e organização de computadores é o


estabelecimento da interface entre o hardware e o software. É possível conhecer-se vários

12-7
aspectos de software, sem que se tenha familiaridade com o detalhamento do hardware
associado; da mesma forma, é possível projetar-se partes de hardware sem conhecer suas
capacidades a nível do software. Entretanto, os projetistas da arquitetura de computadores
precisam conhecer hardware e software, pois estas áreas influenciam-se mutuamente.

Uma das definições estabelecidas a nível desta interface é a escolha das funções que serão
implementadas no hardware e quais utilizarão o software. Reflexos destas definições serão
sentidas na maior ou menor facilidade de programação e na velocidade de execução (de
obtenção de respostas) destas funções. Adicionalmente, podem existir estruturas de
arquitetura para apoiar funções do sistema operacional ou mecanismos que auxiliem na
detecção de erros de programação.
Existem três níveis básicos de implementação de funções de máquina no computador:

• por hardware, fixas pelo projeto lógico dos circuitos;


• por microcódigo, que consiste em um nível intermediário entre o hardware e o
software, e que pode conceitualmente ser enxergado como um conjunto de programas
escritos em baixíssimo nível (especificação direta de sinais de controle) que executam
diretamente sobre o hardware;
• por software, especificadas em uma linguagem de baixo nível.
Funções complexas podem ser obtidas a partir da composição de outras dos níveis
inferiores.
O nível de problemas quando da definição da interface varia bastante em complexidade.
Assim como é necessário definir detalhes de implementação de instruções, também é
necessário determinar a arquitetura global da máquina. Há, por exemplo, arquiteturas
dirigidas a determinadas linguagens, que utilizam combinações implementadas em hardware
e firmware para estender a arquitetura de uma máquina na direção das formas utilizadas pelas
linguagens de programação.

Assim, grande parte das arquiteturas atuais são influenciadas por conceitos de estruturas de
programação encontradas em linguagens de programação de alto nível e por funções comuns
aos sistemas operacionais. Entretanto, existem divergências sobre quanto deve pesar esta
influência e se ela deve ser mais motivada pelas características convenientes às linguagens ou
aos sistemas operacionais. Esta é uma questão que resta a resolver.

1 2 . 6 Sistemas operacionais

Na primeira geração de computadores, quando a programação era feita quase que


exclusivamente que através de chaves e lâmpadas, os sistemas operacionais eram
praticamente inexistentes. A complexidade de tal programação, entretanto, motivou o
desenvolvimento de uma grande série de programas, analisados na seção anterior. Um
quadro típico da década de 60, quando o sistema de entrada e saída era baseado em cartões
perfurados, é descrito a seguir:

• o programador perfurava o seu programa fonte (por exemplo, na linguagem


FORTRAN) em um conjunto de cartões.

• o programador pegava os cartões do compilador FORTRAN, marcados de verde para


serem visualmente distinguidos dos demais, os colocava na leitora de cartões e
carregava o compilador para a memória.
• a seguir, com o compilador na memória, o programador colocava o programa fonte
(em cartões incolores) na leitora. O compilador lia estes cartões e, caso não existissem
erros, perfurava uma série de cartões (vermelhos) com o código objeto gerado.

12-8
• terminada a tarefa do compilador, o programador colocava o carregador (em cartões
rosa) na leitora e carregava-o para a memória.

• com o carregador na memória, o programador colocava agora seu programa objeto na


leitora, seguido dos cartões correspondentes às bibliotecas utilizadas (em diversas
cores específicas)
• o carregador lia todos estes cartões, preparava o programa na memória e transferia o
controle do computador para este programa.

• o programador colocava na leitora os cartões contendo os dados a serem manipulados


pelo programa, que lia estes cartões e gerava relatórios em uma impressora.
• existindo erros na programação, todo o processo tinha que se refeito (e um dos
pesadelos da época era a queda dos cartões no chão, embaralhando-os).
Este sistema de cartões multicoloridos, embora facilitasse em muito o uso do computador,
ainda era muito insatisfatório. O programador tinha que comandar manualmente todo o
processo, e grande parte do tempo do computador era desperdiçado lendo e perfurando
cartões. Conforme a demanda por tempo de processamento, memória, dispositivos de
entrada e saída e quantidade de dados aumentava, a gerência eficiente destes recursos tornou-
se crítica. Todos os recursos de computação eram na época muito caros e valiosos, e tinham
de ser eficientemente aproveitados para não gerar desperdícios.
Foi nesta época que os sistemas operacionais foram desenvolvidos e refinados, sendo os
precursores dos sistemas utilizados hoje em dia. Um dos mais utilizados na época era o
sistema operacional baseado em lotes (batch operating system ), que permitia que um certo
número de tarefas (jobs ) fossem colocadas juntas na leitora de cartões. Assim, por exemplo,
o exemplo descrito acima poderia ser realizado pelo seguinte conjunto de cartões:
//EXAMPLE JOB DONOVAN, T168,1,100,0
//STEP1 EXEC FORTRAN, NOPUNCH
READ 9100,N
DO 100 I = 1,N
I2 = I * I
I3 = I * I * I
100 PRINT 9100, I, I2, I3
9100 FORMAT (3I10)
END
/*
//STEP2 EXEC LOAD
/*
//STEP3 EXEC OBJECT
10
/*
O primeiro cartão define um processo que o usuário de nome Donovan deseja realizar; o
segundo cartão indica que os cartões seguintes devem ser processados pelo compilador
FORTRAN (até o cartão de fim de arquivo, marcado com /*). O passo seguinte é a chamada
do carregador, sem nenhum outro cartão especial. O último passo comanda a execução do
programa objeto recém gerado, e fornece os dados a serem lidos (no caso o número 10).

Todos os cartões são interpretados pelo sistema operacional, que inclui todos os programas
do sistema mencionados anteriormente, além de outros que supervisionam e controlam as
operações de todos os programas no computador.

O sistema operacional realiza as seguintes funções:


• aloca espaço de memória e carrega programas para a execução;

12-9
• fornece serviços para a obtenção de dados de entrada e para a produção de dados de
saída;
• fornece recuperação automática para diversos tipos de erros, como erro na leitura de
um dispositivo de entrada, ou erro de “overflow”.

Nos sistemas operacionais simples, a memória totalmente alocada a um único programa.


Assim, se este programa não utiliza toda a memória, parte deste recurso não é utilizado. Nos
tempos atuais isto não é considerado um problema sério, mas até a década de 70 a memória
era um recurso extremamente caro e que não poderia ficar ocioso. Para usar a memória na
sua integridade, desenvolveram-se os sistemas operacionais multiprogramados.

Em sistemas multiprogramados, dois ou mais programas são carregados na memória


(em áreas diferentes, naturalmente) e o computador os executa simultaneamente. Havendo
apenas uma UCP, o sistema só pode processar, em cada instante, tarefas relativas a um
usuário. Mas o sistema operacional atende a todos com uma rotatividade intensa, de tal forma
que ele parece estar executando todos em paralelo.
Portanto, nestes sistemas, muitos programas podem residir simultaneamente no computador.
O sistema operacional aloca os diversos recursos computacionais para os programas
selecionados e mantém as atividades de chaveamento dos recursos durante o funcionamento
do sistema. Exemplificando, enquanto um programa está sendo executado na UCP, um
outro programa pode estar recebendo dados de um dispositivo periférico (fita magnética, por
exemplo), e um terceiro pode estar imprimindo dados.
Em tais sistemas um dos principais problemas é a gerência eficiente de memória. Com a
constante alocação e liberação de memória, podem surgir áreas de memória muito pequenas
para conterem um programa. O surgimento destes “buracos” na memória é conhecido como
fragmentação. A fragmentação tem sido minimizada por diversas técnicas. Uma delas
consiste em rearranjar os endereços do programas (realocação dinâmica), movendo-os de
lugar e assim reunindo todas as porções não utilizadas em uma única região contínua de
memória.
Outra técnica é a de paginação: por este método, os programas são sub-divididos em porções
iguais ou páginas, e a memória é dividida em porções denominadas blocos. Na hora da
carga do programa, as páginas são carregadas nos blocos. Em um sistema de paginação
simples todas as páginas do programa são carregadas na memória. Já em um sistema mais
sofisticado de paginação por demanda, um programa pode ser executado sem que todas
as suas páginas estejam na memória. As páginas são carregadas conforme elas são
necessárias, isto é, quando elas são referenciadas (demandadas).

Em sistemas onde há mais do que uma UCP, há a possibilidade real de que sejam executadas
duas ou mais instruções ao mesmo tempo: neste caso, tem-se multiprocessamento, ou
seja, cada processador pode executar um programa diferente em paralelo (ao mesmo tempo)
aos demais.

Existem sistemas nos quais o tempo do processador é partilhado entre diversos usuários: são
os sistemas de time-sharing (ou “de divisão de tempo”). Nestes, muitos usuários
comunicam-se com o sistema computacional através de terminais remotos. O sistema
operacional aloca a cada um, ou seja, a cada “job”, um período de tempo (“time-slice”) com
base em considerações de prioridade. O “job” é uma unidade de trabalho especificado
aplicado na execução de uma tarefa de processamento de dados. Assim, durante estes
períodos de tempo, o computador faz com que o computador processe um “job” até que
ocorra uma das seguintes condições:
• o “job” é completado;
• um erro é detectado;
• ocorre solicitação ou necessidade de uma entrada/saída;
• o período de tempo termina.

12-10
Então o processador é designado para o “job” de mais alta prioridade. Nos dois primeiros
casos, o “job” deve ser removido da memória. Nos dois últimos, o “job” é suspenso
temporariamente.

O sistema operacional contribui para o usos mais eficiente dos recursos de hardware pelo
gerenciamento dos recursos de memória. Por exemplo, se um programa não pode ser
acomodado inteiramente na memória devido ao seu tamanho, o sistema operacional pode
dividi-lo em partes denominadas de páginas ou segmentos, transferindo-os gradualmente
da memória secundária para a principal.

O efeito do sistema operacional na gerenciamento do sistema computacional visa melhorar


sua eficiência, a qual é avaliada pelo “throughput”. “Throughput” é a quantidade de
processamento que o sistema realiza com sucesso durante um intervalo de tempo
especificado. Pode servir como medida de avaliação tanto para hardware como para o
software. A contribuição do sistema operacional neste sentido é resultante da eficiência das
facilidades existentes no seu código.

1 2 . 7 Funções básicas dos sistemas operacionais

Um sistema operacional é um conjunto de programas que permite ao computador controlar


os recursos, executar programas e gerenciar dados. Inclui todos os programas do sistema
mencionados anteriormente, além de outros que supervisionam e controlam as operações de
todos os programas no computador. Do ponto de vista do usuário, a função do sistema
operacional é auxiliá-lo na mecânica de resolução de problemas.

O sistema operacional tem três encargos principais: gerenciar a execução de programas e


ações realizadas na memória, gerenciar o armazenamento de arquivos e gerenciar as
atividades de E/S. Para tanto, ele detém três funções básicas, que são:
• controlar os recursos do sistema de computação;
• executar os programas do computador;
• gerenciar dados.

Estas funções são executadas sem que o programador precise estar instruindo o sistema
operacional a fazê-lo, a cada momento. Na verdade, o sistema operacional já existe como
uma série de programas que fazem parte da máquina e que permitem uma operação
razoavelmente confortável para o programador de aplicações. Os programas de controle
minimizam a intervenção do operador de tal forma que as operações do computador fluem de
forma suave e contínua. O programa mais importante no sistema é o supervisor, cuja maior
parte reside na memória (está sempre lá). Ele controla o sistema operacional inteiro e chama
outros programas do sistema operacional (do disco), quando necessário, para que eles
permaneçam na memória enquanto forem executados. Após, eles retornam para o disco, para
que se tenha uso eficiente do espaço de memória.

O sistema operacional aumenta a eficiência de duas maneiras:

• ele atua como promotor da cooperação entre os usuários do sistema, ajudando-os a


fazerem o melhor uso possível dos recursos computacionais de forma que todos tirem
proveito.

• ele chama tradutores e outros programas para que se encarreguem de tarefas comuns
(usuais). Isto libera os programadores de aplicações de tarefas rotineiras e repetitivas.
A fim de cumprir as funções acima listadas, o sistema operacional executa atividades tais
como:

• operações de seqüenciamento e escalonamento de “jobs” (alocação de espaço de


memória e carga de programas para a execução), e controle de tráfego: o sistema recebe

12-11
os diferentes “jobs”, e com base em suas características e necessidades decide sobre
sua execução (prioridades, tempo de execução, recursos disponíveis, etc). Quando é
possível executar-se entrada/saída simultaneamente à execução de um programa, todas
estas funções são escalonadas pelo controlador de tráfego.

• programação de entrada e saída: executa diretamente as ações relacionadas às operações


de entrada e saída, quando o canal de entrada e saída tem seu conjunto próprio de
instruções especializadas, necessitando apenas de instruções simples do usuário
referentes a estas operações.

• auto-proteção (contra o usuário) e proteção do usuário com relação aos demais: oferece
proteção ao usuário, evitando que seus programas, bases de dados ou arquivos sejam
modificados por ações maliciosas ou acidentais. Igualmente, o próprio sistema
operacional deve assegurar sua auto-inviolabilidade.

• gerenciamento de armazenamento secundário: é o controle do uso de discos, fitas e


outros meios de armazenamento secundário para os programas e dados do usuário,
sendo uma tarefa realizada pelo sistema operacional, transparente do ponto de vista do
usuário.

• manipulação de erros: o sistema operacional deve realizar ações específicas com relação
aos diversos tipos de erros que podem ser causados durante a operação e uso da
máquina, que podem variar desde o envio de aviso ao usuário até a correção ou
modificação de parâmetros para poder prosseguir na operação.
Estas funções precisam ser compatibilizadas com as estruturas de hardware, de modo que se
obtenha o melhor compromisso em objetivos conflitantes tais como eficiência, ciclos rápidos
de execução, e conveniência do usuário. Para tanto são usadas diversas técnicas de
implementação; algumas são comentadas ao longo deste material.

1 2 . 8 Processos e escalonamento
Um processo pode ser visto como uma seqüência ou conjunto de operações que realizam
uma tarefa computacional, como por exemplo um processo de leitura, impressão, execução,
etc. Um processo computacional pode ser seqüencial com um conjunto de operações
ordenadas em tempo ou concorrente com operações paralelas. O conceito de processos é
importante para sistemas operacionais porque a realização de cada processo pode representar
a execução de uma tarefa isolada, complementar ou concorrente escalonados pelo sistema
operacional.
Processos seqüenciais são caracterizados por uma ordenação da execução de suas tarefas no
tempo, portanto facilitando a administração de um conjunto de tarefas por um sistema
operacional. Em processos concorrentes é possível ter duas ou mais operações em paralelo.
Os processos concorrentes existem por causa da competição no uso dos recursos de um
computador como, por exemplo, usos da memória, UCP, leitoras, etc, resultando em
escalonamento de operações para minimizar qualquer conflito entre processos como também
reduzir o tempo de execução. Isto é feito normalmente através de processos como, por
exemplo, leitura pelos canais, enquanto a UCP executa outros processos. A operação
spooling é baseada sobre o princípio de sobreposição de processos com o auxílio de canais
ou dispositivos especiais. Em geral, o compartilhamento dos recursos de um computador em
tempo e espaço necessita de um módulo de escalonamento associado aos sistemas
operacionais multiprogramáveis.
Existem duas formas principais de escalonamento: escalonamento sem e com preempção. O
escalonamento sem preempção assume que um processo já em posse de um recurso (UCP,
leitora, impressora, etc) não é interrompido até o final da execução do processo. Para
implementar tal política, são usados modelos primitivos na decisão de “enfileiramento” dos

12-12
processos, tais como: primeiro a chegar, primeiro a ser servido, ou processos com mínimo
tempo de execução.

Com preempção, um processo pode ser interrompido em execução para transferir controle de
um recurso para outro processo ou atender às necessidades do sistema. Este tipo de
escalonamento é muito empregado em sistemas de multiprogramação onde existe
compartilhamento de espaço e tempo por todos os processos. Em sistemas de
multiprogramação, os modelos para determinar a política de escalonamento podem ser
bastante complexos. A interrupção, bastante usada em sistemas multiprogramáveis, tem a
função de preempção de um processo momentaneamente em posse de um recurso de um
recurso, tal como UCP, dispositivos, etc.

1 2 . 9 Carga do sistema (inicialização da máquina)

Denomina-se de carga do sistema, à operação que tem por objetivo colocar o computador em
condições de funcionamento. Consiste, fundamentalmente, em carregá-lo com rotinas
essenciais ao atendimento dos diversos programas de aplicação que lhe serão posteriormente
submetidos. Esta operação é comumente denominada de bootstrap, referindo-se
informalmente ao procedimento como “dar o boot” na máquina.
Esta operação envolve: a colocação das primeiras instruções na memória a partir de um
comando específico: nas máquinas modernas, este comando já vem embutido na inicialização
da máquina; nas antigas, era disparado por uma tecla ou botão especial, ou estas instruções
eram carregadas manualmente na memória da máquina. Este programa dispara a leitura (de
um disquete, do disco, de uma memória de armazenamento permanente, por exemplo) de um
programa carregador que então é posicionado na memória; a partir deste, novos programas
ou rotinas são sucessivamente carregados ou posicionados na memória, para executar as
ações subseqüentes. Esta carga completa é denominada de boot inicial do sistema. O
computador não tem a capacidade de reter a informação que está na memória principal
quando é cortada a energia elétrica: assim, esta operação é repetida a cada nova ligação da
máquina.

1 2 .1 0 Multiprogramação
Assim, como visto na unidade anterior, a multiprogramação se refere à existência de mais
do que um programa em diferentes partes da memória principal ao mesmo tempo. Seu
principal objetivo é a eficiência computacional. Uma técnica relacionada é a multi-tarefas, ou
a existência de diversas tarefas que são parte do mesmo “job” e podem ser executadas
simultaneamente.

Em sistemas multiprogramados, dois ou mais programas são carregados em diferentes


áreas da memória, na expectativa de que eles vão gerar uma quantidade significativa de
trabalho para o computador. Se este trabalho gerado exceder a capacidade do computador, o
resultado é que a máquina (em princípio) não deverá ficar parada, esperando novas tarefas.
Assim, o programa supervisor executa o trabalho de alocação das diferentes unidades pela
manutenção de listas de passos que estão prontos para execução em cada uma das unidades.
Quando uma unidade completa um passo, o supervisor pode consultar a sua lista para um
novo trabalho. No caso das unidades de entrada/saída, a conclusão de uma passo é indicado
por uma interrupção, que sinaliza à UCP para parar temporariamente o programa atual e para
alocar mais trabalho à unidade de E/S, se houver. No caso da CPU, a conclusão é indicada
quando o programa na UCP ou chega ao final do job ou requisita buffers de E/S que ainda
não estão disponíveis – isto é – se uma entrada foi requisitada, e o sistema ainda não
preencheu o buffer, ou se uma saída foi requisitada, o sistema ainda não esvaziou os buffers
anteriores de tal forma que haja lugar para os novos.

Neste gerenciamento de execução de jobs, há problemas envolvidos tais como:

12-13
• quando ocorre a interrupção nas atividades de um job, visando transferir o
processamento para outro, é necessário assegurar a guarda de valores contidos nos
registradores e memória, para posteriormente poder retomar as atividades;

• determinação de qual(is) job(s) deve(m) ser carregado(s) na memória quando há


diversos aguardando na fila de execução, e qual tarefa deve ser alocada a uma unidade
em particular, quando há diversas tarefas que aguardam por programas que já estão na
memória. A solução emprega algoritmos de escalonamento e critérios de prioridades;

• custo de memória, já que há necessidade de maior espaço de memória do que em


sistemas onde somente um job pode ser rodado em cada momento;
• ainda outros problemas dizem respeito a questões tais como: alocação de memória,
relocação e proteção.

A fim de racionalizar o compartilhamento pa UCP (processador) entre os diversos jobs


(processos) utiliza-se um scheduler, ou seja, um elemento de software que realiza duas
funções básicas:

1. selecionar um processo entre os prontos para executar (ready) para ser o próximo a
ganhar o controle da UCP.
2. determinar a fatia máxima de tempo de processador (time-slice) que o processo pode
utilizar antes de ir novamente para a fila dos processos prontos para executar.
A Figura 12.1 ilustra as transições de estado que um processo pode sofrer. Os processos que
reúnem todas as condições necessárias para serem executados estão no estado “pronto”
(ready, em inglês). Quando um dos processos deste grupo ganha o controle do processador,
ele transiciona para o estado “executando” (running). Ele sairá deste estado ou quando a sua
fatia de tempo terminar (neste caso ele retorna para o grupo pronto) ou quando necessitar de
um evento externo à UCP (neste caso vai para o grupo bloqueado). Um processo em estado
“bloqueado” (blocked) permanece neste estado até que o evento esperado ocorra (como o
término de uma operação de E/S, a execução de uma determinada operação por outro
processo, etc). Quando o evento ocorre, o processo volta ao grupo “pronto”.

Processo precisa esperar


pelo término de um evento
Processo selecionado Execu- (por exemplo, E/S)
tando

Pronto Processo interrompido Bloqueado


(fim da fatia de tempo)

O evento esperado pelo processo ocorreu


Figura 12.1 - Estados de um processo

A figura não ilustra nem o “nascimento” nem a “morte” de processos. Quando um processo é
inicializado, ele vai primeiramente para a fila dos processos “prontos”, concorrendo com os
demais ao uso da UCP. Quando um processo encerra sua execução, ele simplesmente não
retorna ao grupo “pronto”.

12-14
Diversos problemas podem ocorrer no escalonamento dos processos, principalmente
relacionados à sincronização entre processos. Um deste problemas é o da “corrida” (race),
quando a sincronização é tão crítica que diversas ordens de escalonamento podem produzir
diferentes computações (resultados diferentes). Outro problema é o do “deadlock”, quando
diversos processos se bloqueiam mutuamente, e de tal forma que cada processo fica
esperando por eventos que deveriam ser gerados pelos outros processos bloqueados. Estes
problemas podem ser resolvidos de duas maneiras básicas distintas: por cooperação ou por
comunicação. Na comunicação, os processos trocam mensagens entre si, de forma a se
sincronizarem e impedir o surgimento de problemas. Na cooperação, existem recursos
críticos, que devem ser utilizados única e integralmente por um processo antes de passarem
para outros processos. Estes recursos de “exclusão mútua” normalmente tem seu acesso
controlado por variáveis do tipo “semáforo”. O detalhamento exato dos problemas acima,
assim como as metodologias e soluções possíveis serão analisados em outras disciplinas.

1 2 .1 1 Multiprocessamento
O multiprocessamento se refere aos sistemas onde há duas ou mais UCPs em um único
sistema computacional; assim, há a possibilidade real de que sejam executadas duas ou mais
instruções ao mesmo tempo. Estas UCPs estão conectadas à mesma memória, de tal forma
que elas podem estar executando partes do mesmo ou de diferentes programas. O uso de
múltiplas UCPs visa incrementar a capacidade de processamento do sistema, freqüentemente
avaliado em mips (millions of instructions per second).
Ainda há os sistemas nos quais várias UCPs, cada uma com a sua própria memória, podem
ser interligadas através de canais, cada uma assemelhando-se a um dispositivo de E/S do
ponto de vista dos demais computadores. Isto não é multiprocessamento: é uma sistema
multicomputador, também denominado de rede de computadores. Nestes sistemas, há
distribuição dos jobs aos computadores que estão com tempo de processamento disponível
nas modalidades requisitadas, o que é caracterizado como distribuição de carga.

1 2 .1 2 Exemplos de sistemas operacionais

Os sistemas operacionais vêm normalmente incorporados às máquinas (computadores),


sendo cobrados à parte ou não. Atualmente, há tendência no uso de sistemas operacionais
genéricos, isto é, sistemas operacionais cuja natureza permite que ele opere com sistemas de
computadores de diferentes fabricantes. Estes sistemas tem sido freqüentemente criados por
companhias de software e não por fabricantes de máquinas. Exemplos destes casos são o
UNIX e o MS-DOS.
O UNIX foi desenvolvido em 1971 por Ken Thompson e Denis Ritchie na AT&T Bell
Laboratories para uso nos minicomputadores DEC da Bell. Os projetistas surpreenderam-se
com a aceitação no mercado externo à Bell. Como razões para o fato, apontam: o uso
disseminado do software por faculdades e universidades, cujos usuários repassaram o uso
posteriormente às indústrias; outra razão foi a enorme redução no preço por cópia, em 1981,
sendo estas comercializadas por US$ 40. Este é um sistema operacional multi-usuário, com
divisão de tempo (time-sharing) que, embora inicialmente implementado para
minicomputadores, atualmente roda em máquinas de grande porte e em alguns
microcomputadores. Entre os problemas relacionados ao UNIX, apontam-se: sua dificuldade
de utilização (do ponto de vista do usuário, não é “user-friendly”, embora esta característica
tenha sido melhorada pelo uso de menus tradicionais); além disto, faltam ao UNIX algumas
características de segurança sofisticadas.
Para computadores pessoais, o sistema operacional mais popular é o MS-DOS (Microsoft
Disk Operating System; Bill Gates, Microsoft). Este sistema foi escolhido pela IBM para
incorporá-lo aos seus computadores pessoais, em detrimento do CP/M (Gary Kildall, Digital
Research) que já se encontrava em uso, na época. Neste sistema, os programas são
executados pela emissão de um comando, ou seja, um nome que chama o programa

12-15
desejado. Os comandos internos do DOS são colocados na memória do computador quando
o usuário liga-o. Os demais, que residem em disco, são chamados de comandos externos do
DOS.

De forma geral, o software escrito para rodar em um sistema operacional, não rodam em
outro. Assim, os projetistas de software tentam maximizar as vendas de seu produto
escrevendo programas para os sistemas operacionais mais utilizados. Alguns sistemas
fornecem figuras e/ou designações simplificadas aos invés de comandos ou símbolos
simples como o PROMPT (>) do DOS. O efeito destas figuras é o de apresentar um aspecto
mais “amigável” para o usuário, existindo tal como uma “roupagem” ou shell ao redor do
sistema operacional. Assim, eles criam um ambiente confortável para o usuário, que não
precisa estar memorizando comandos específicos.

1 2 .1 3 Redes de computadores
Computadores podem ser interligados entre si, de forma a compartilhar recursos (memória,
periféricos, UCP e informação). Se os computadores estão próximos entre si (na mesma sala
ou no mesmo prédio), a rede é denominada de rede local ou LAN (Local Area Network). Se
por outro lado os computadores estão geograficamente distantes, tem-se uma WAN (Wide
Area Network).
Ao nível de hardware, a comunicação é realizada através de circuitos especiais, que
transformam os dados a serem transmitidos em sinais elétricos. Como meio físico utilizam-se
desde pares telefônicos até cabos coaxiais, fibras óticas, enlaces de microondas e até satélites
de comunicação. Ao nível de software desenvolveram-se diversos métodos de comunicação
(protocolos), que definem como esta comunicação é realizada e comos os dados
intercambiados devem ser interpretados. Frente a sua complexidade, redes modernas são
projetadas de forma altamente modularizada. A maioria das redes se encontra organizada em
uma série de camadas hierárquicas, onde cada camada utiliza os serviços definidos da
camada inferior e fornece uma outra série de serviços, de mais alto nível, para a camada
superior. A International Standards Organization (ISO) criou o modelo de referência OSI
(Open Systems Interconection), que define sete destas camadas em cada máquina:

1. Físico: define as características dos equipamentos e os requisitos para a realização


das ligações.
2. Enlace (ou ligação): define os meios e os procedimentos para a transmissão de
blocos de informação e controle dos possíveis erros que possam ocorrer.

3. Rede: define o intercâmbio de informação dentro da rede. Trata do agrupamento da


informação em pacotes, do endereçamento dentro da rede e da detecção e correção
de erros.
4. Transporte: trata da transferência de mensagens, do agrupamento e decomposição
dos pacotes de dados. Outra atribuição é a otimização do uso da rede, selecionando
as conexões adequadas.

5. Sessão: controla as operações realizadas sobre os dados, a fim de assegurar sua


integridade com respeito ao uso compartilhado dos mesmos. Neste nível agrupam-
se as mensagens relacionadas entre si (estabelecendo uma sessão).

6. Apresentação: trata da organização das entradas e saídas, definindo os formatos


necessários aos terminais, aos arquivos e aos dados, a fim de que possam ser
utilizados pela sessão e pela aplicação do usuário.

7. Aplicação: consiste no controle e supervisão dos processamentos dos usuários que


se intercomunicam.

12-16
Do ponto de vista do sistema operacional, tem-se um recurso a mais a ser gerenciado: a rede.
Serviços que não estão disponíveis no computador local podem estar acessíveis via rede e,
para utilizá-los de forma adequada, o sistema operacional deve ter conhecimento deles. Tem-
se assim os sistemas operacionais distribuídos, onde os recursos a serem gerenciados
não estão concentrados em um único computador, mas sim espalhados ao longo da rede.

O processador deixa de ser uma entidade única no sistema. Além do tradicional aspecto do
escalonamento de processos (qual processo será executado pelo processador), surge a
questão do escalonamento de processadores (qual dos processadores disponíveis irá executar
determinado processo).Uma outra consequência da multiplicidade de processadores é a
possibilidade de continuidade do processamento após a ocorrência de falhas em um
determinado processador. Como provavelmente ainda haverá réplicas desse recurso na rede,
os processos afetados pela falha podem continuar após migrarem para um processador não
falho.

Tais vantagens, entretanto, são pouco exploradas atualmente. Esse fato decorre da pouca
utilização de sistemas operacionais distribuídos nesse tipo de ambiente. É mais frequente o
emprego de sistemas operacionais centralizados tradicionais, que não foram projetados
especificamente para ambientes distribuídos, com extensões para suportar algumas operações
remotas básicas (como transferência de arquivos e execução remota de processos, por
exemplo).
Por um sistema distribuído se entende atualmente um sistema que consiste de múltiplos
processadores que não compartilham memória primária e se comunicam por mensagens
através de uma rede de comunicação. Programas distribuídos podem contar com quatro tipos
de processos: clientes, servidores, filtros e pares. Clientes e servidores interagem usualmente
de forma síncrona: um cliente envia uma mensagem de requisição de serviço a um servidor
local ou remoto; o serviço é executado pelo servidor, que retorna os resultados ao cliente
(que espera bloqueado). Um servidor pode ser projetado para atender múltiplas requisições
concorrentemente. Processos filtro recebem, processam e enviam adiante os dados obtidos.
Processos pares interagem com mensagens para cooperar no atendimento a requisições.
Nos dias de hoje os ambientes computacionais da maioria das organizações apresentam um
realidade heterogênea. Esta heterogeneidade, que pode ser definida com a diversidade
existente em sistemas de computação, manifesta-se especialmente sob os seguintes aspectos:
• arquitetura da máquina: processadores, memória e dispositivos de E/S. As arquiteturas
atuais diferem em uma série de aspectos, dos quais o principal é a família do elemento
processador utilizado no equipamento. Por família entenda-se um conjunto de
elementos processadores com certo grau de compatibilidade entre si. Exemplos são os
microprocessadores Intel 80x86 e os Motorola 680x0. Uma parcela das estações de
trabalho existentes atualmente utiliza processadores RISC (Reduced Instruction Set
Computer), embora as linhas de processadores de cada fabricante sejam incompatíveis
entre si. Há ainda outros tipos de processadores, tal como os Transputers, utilizados
em máquinas paralelas.
• sistemas operacionais: chamadas do sistema, interface com o usuário (interpretador de
comandos e ambientes de janelas) e mecanismos de comunicação entre processos;
• redes de comunicação: protocolos de comunicação e interfaces de rede;

• linguagens de programação e compiladores: diferenças entre implementações de uma


mesma linguagem e uso de diferentes linguagens.
Um sistema operacional de rede pode ser definido como a soma das camadas de software
escritas em cada máquina (host, ou hospedeiro) para propiciar comunicação e
compartilhamento de recursos entre a máquina local e diferentes máquinas remotas. Tanto o
computador local como os equipamentos remotos que compõe a rede são entidades
autônomas.

12-17
Bibliografia

• Alcade, E.; Garcia, M.; Peñuelas, S. Informática Básica. Makron Books, São
Paulo, 1991.
• Akonteh, Benny. Introdução à organização e arquitetura de computadores digitais.
AIT, Brasília, 1983.

• Axmann, Hans-Peter. Einführung in die technische Informatik. Springer Verlag,


Wien, 1979. Capítulos VI (Informationsdarstellung) e VII (Rechengrundlagen-
Computerarithmetik).
• Capron, H. L. Computers: Tools for an information age. The Benjamin/Cummings
Pub. Co., Redwood City, 1990.
• Capron, H. L.; Williams, B. K. Computers and data processing. The
Benjamin/Cummings Pub. Co., Menlo Park, 1984.
• Donovan, J. J. Systems programming. McGraw-Hill Kogakusha, Tokyo, 1972.
• Gear, Wiliam. Computer organization and programming. McGraw-Hill
Kogakusha, Tokyo, 1969.
• Hayes, John P. Computer architecture and organization.MacGraw-Hill,
Tokyo,1978.
• Hamacher, V.C.; Vranesic, Z.G.; Zaky, S.G. Computer Organization. MacGraw-
Hill, Tokyo, 1978.
• Lorin, Harold. Introdução à arquitetura e organização de computadores. Editora
Campus, Rio de Janeiro, 1985.

• Mano, M. M. Computer system architecture. Prentice-Hall, Englewood Cliffs,


1976.

• Velloso, F. C. Informática - uma introdução. Editora Campus, Rio de Janeiro,


1986.

• Langdon Jr., Glen George; Fregni, Edson. Projeto de computadores digitais.


Editora Edgar Blücher Ltda, São Paulo, 1974.
• Kraft, George; Toy, Wing. Mini/Microcomputer Hardware Design. Prentice-Hall,
New Jersey, 1979.
• Bartee, Thomas C. Fundamentos de Computadores Digitais. Editora Guanabara
Dois, Rio de Janeiro, 1977.

• Flores, Ivan. Computer Logic: the functional design of digital computers. Prentice-
Hall, New Jersey, 1960. Capítulos 6 (Machine arithmetic), 7 (Number systems and
counting) e 8 (Machine Languages).

1
• Flores, Ivan. Computer Design. Prentice-Hall, New Jersey, 1967. Capítulos 8
(Numbers) e 9 (Codes).

• Maley, Gerald A.; Earle, John. The logic design of transistor digital computers.
Prentice-Hall, New Jersey, 1963. Capítulos 2 (Number systems and codes) e 7
(Arithmetic operations).
• Coonen, Jerome T. An implementation guide to a proposed standard for floating-
point arithmetic. Computer, vol.13, nº1, january 1980, pp.68-79.

• Weber, Raul Fernando. Processador Pipeline com arquitetura bit-slice. Pós-


Graduação em Ciência da Computação, UFRGS, janeiro 1980. Dissertação de
mestrado. (Algoritmos de aritmética binária).

2
Apêndice
UM
Utilização dos simuladores e depuradores

As características e comandos de depuração aqui apresentadas são válidas para os


simuladores dos pseudo-computadores Neander e Ahmes, estudados nesta disciplina. Estes
comandos serão válidos também para outras máquinas utilizadas na disciplina subseqüente
de Arquitetura e Organização de Computadores I. Estes simuladores foram desenvolvidos
para computadores PC e compatíveis, e permitem a execução e depuração de programas em
linguagem de máquina escritos para os computadores NEANDER e AHMES. Os
simuladores desenvolvidos incorporam grande parte das funções encontradas em
depuradores comerciais, tais como:
• edição de memória,
• execução passo a passo,
• inserção de pontos de parada (break-points),
• janela de memória e
• visualização do conteúdo dos registradores.
Exercícios usando os simuladores visam principalmente familiarizar o usuário com os
conceitos básicos de arquitetura de computadores, através de programação em linguagem de
máquina e simbólica, e com as técnicas de depuração de tais programas.

A.1 Simulador

Os simuladores existem atualemnte em duas versões: para DOS e para Windows. Embora
estas duas versões sejam funcionalemnte equivalentes, recomenda-se fortemente o uso da
versão Windows, pois a versão DOS não será mais atualizada.
Após carregado o simulador é mostrada a tela de trabalho. Preste bastante atenção a ela. A
tela de trabalho é formada por campos de edição, campos de informação estática (cujo
conteúdo não se altera) e campos de informação dinâmica (cujo conteúdo se altera em
resposta a uma ação do usuário).

A.2 Formato da tela


Os elementos na tela do simulador são os seguintes:

• Janela de Dados (Memória)


mostra o conteúdo de 16 posições da memória, interpretadas como dados, e permite
eventualmente alterar tais valores.

• Registradores
para o computador NEANDER é mostrado o conteúdo do acumulador (AC), do
registrador de status (bits N e Z), do apontador de instruções (PC) e do registrador de
instruções (RI). Para o AHMES apresenta-se a mesma informação, sendo a única
diferença o maior número de bits do registrador de status (bits N, Z, C, V e B). Os
demais computadores possuem outros conjuntos de registradores.

A-1
• Janela de Programa (Memória)
mostra o conteúdo de 16 posições da memória, interpretadas como programa, e permite
eventualmente alterar tais valores.

• Break-point
um ponto qualquer do programa, escolhido pelo usuário como ponto de parada (o
simulador para no endereço indicado, sem executar a instrução apontada). O valor do
break-point é inicializado com o endereço superior de memória (255 decimal ou FF
hexa).

• Tabela de códigos de instrução


para ajudar a quem possui memória volátil, mostra os mnemônicos e os códigos das
instruções.

• Menu de comandos
mostra os comandos aceitos pelo simulador/depurador. O caractere sublinhado em cada
comando pode ser usado para ativação via teclado.

Os campos listados abaixo são de informação estática:


- tabela de códigos de instrução (pode ser ativada ou apagada pela tecla F1)
- menu de comandos

Os campos a seguir são de informação dinâmica:


- registradores
- break-point
- linha de mensagens
Os demais são campos de edição:
- janela de dados
- janela de programa

A.3 Entrada numérica


Entradas numéricas são necessárias em duas situações distintas: para fornecer um endereço
solicitado pelo simulador ou para editar posições de memória. Na digitação de valores
numéricos, valem as seguintes regras:

• valores maiores que 25510 (decimal) e FF16 (hexadecimal) não são aceitos,
• valores negativos não são aceitos (devem ser entrados em complemento de dois)
• as teclas de edição são válidas para alterações e correções
• a tecla ENTER termina a entrada do número.

Quando é necessária a entrada de um endereço, o simulador fornece um valor padrão, de


acordo com a situação atual. Para aceitar este valor, basta digitar ENTER . Caso contrário, o
início da digitação de um novo número anula o valor padrão. Durante a entrada de valores na
memória, a utilização da tecla ENTER incrementa automaticamente o endereço, ou seja,
termina a entrada do número e inicia a edição do próximo endereço.

A.4 Comandos de operação

Os comandos que permitem editar e depurar programas usando os simuladores são


apresentados nas próximas seções.

A-2
A . 4 . 1 Hexadecimal x decimal
Todos os valores numéricos (endereços, códigos de instruções, dados) podem ser
representados tanto em hexadecimal como em decimal. O simulador começa sempre a operar
em hexadecimal. Você pode escolher a forma que melhor lhe convém, através dos seguintes
comandos:
Comando “Decimal” ou Icone “0..9”
todos os valores numéricos aceitos e mostrados pelo simulador são decimais.

Comando “Hexadecimal” ou Icone “0..F”


todos os valores numéricos aceitos e mostrados pelo simulador são hexadecimais.

A . 4 . 2 Visualização Simbólica
A Janela de Programa possui uma coluna que permite visualizar as instruções em modo
simbólico, ou seja, em uma interpretação a nível de linguagem assembler. Note-se que o
código da instrução é “desmontado”, sendo apresentado na forma de mnemônico, mas
endereços e operandos são somente visualizados na forma numérica, em decimal ou
hexadecimal (dependendo da base escolhida).
A visualização inicia no endereço inicial da janela, independente do fato deste endereço ser
realmente o início de uma instrução ou não. Por exemplo, considerem-se as duas janelas
abaixo (mostradas somente com 10 posições, em decimal):

Endereço Dado Simbólico Endereço Dado Simbólico


0 32 LDA 128 1 128 JMP 48
1 128 2 48
2 48 ADD 129 3 129 JMP 48
3 129 4 48
4 48 ADD 130 5 130 JMP 16
5 130 6 16
6 16 STA 128 7 128 JMP 240
7 128 8 240
8 240 HLT 9 0 NOP
9 0 NOP 10 0 NOP
10 0 NOP 11 0 NOP

Na janela da esquerda, o endereço inicial é zero, e tem-se um programa que soma três
posições. Ao deslocar-se esta janela de uma posição (com a seta para baixo), tem-se a janela
da direita, cujo conteúdo é o mesmo, mas foi interpretado de forma diferente. Esta
característica não se constitui propriamente um erro, mas é típica da arquitetura de von
Neuman, que permite uma completa liberdade no posicionamento de dados de instruções, e
inclusive no início das instruções. Um computador deste tipo executa automaticamente as
instruções a partir do endereço apontado pelo PC. Cabe ao programador (ou ao sistema
operacional) garantir que o programa inicie em um endereço coerente.

Caso entretanto se deseje iniciar a interpretação simbólica sempre no início do programa


(endereço zero) e não no início da janela de programa, pode-se utilizar para isto o comando
“Mnemônicos relativos”, ou a tecla F2.

A . 4 . 3 Editando um programa na memória


Para alterar o conteúdo da memória, use os comandos de edição, válidos tanto para a janela
de dados como para a janela de programa. Ambas apresentam um campo de edição, que
permite alterar um byte de cada vez.

A-3
A escolha de uma das duas janelas para edição é feita com o mouse , e a movimentação
dentro da janela também. Uma vez ativada uma janela, algumas teclas também podem ser
usadas para movimentação:

Tecla Significado
seta para baixo ou enter posição posterior de memória
seta para cima posição anterior de memória
page up volta 16 posições
page down avança 16 posições
Tabela A.1 - Movimento do cursor na janela de memória durante edição

As duas janelas podem ter seu tamanho redimensionado, para permitir a visualização de mais
de 16 posições.

A . 4 . 4 Inspecionando a memória

O comando “Ir para” permite o deslocamento rápido para qualquer posição da janela de
programa.

A . 4 . 5 Imprimindo porções da memória


O comando “Salver Texto” permite que trechos de programas possam ser transcritos para um
outro arquivo, para fins de documentação. O comando de impressão não envia os dados
diretamente para um impressora, mas sim armazena toda a informação que se deseja imprimir
em um arquivo, a fim de que o usuário complete esta informação com comentários e outros
dados.

O formato da impressão segue a base atualmente sendo utilizada (decimal ou hexadecimal),


ou seja, o formato segue exatamente aquele da Janela de Programa.

A . 4 . 6 Zerando uma área de memória


Uma determinada área de memória pode ser zerada usando o comando “Zerar Memória”.
O simulador solicita o endereço inicial e o endereço final da área de memória a ser zerada.

A . 4 . 7 Movendo blocos
Blocos podem ser movidos na memória através do comando “Copiar Memória”.

O simulador solicita o endereço inicial e o endereço final da área de memória a ser movida e o
endereço inicial da área de destino. Forneça-os. O bloco sempre é deslocado de forma a
manter a sua integridade na posição de destino. Assim, por exemplo, pode-se mover um
bloco que inicie no endereço 10 e termine no endereço 15 para o endereço de destino 11.
Com isto, as posições 11 a 16 receberão os conteúdos originais das posições 10 a 15.
Observação: o bloco fonte não é alterado, a menos das posições que coincidam com o bloco
destino.

A . 4 . 8 Executando um programa

Você pode executar um programa em dois modos: passo a passo ou contínuo. Os dois
modos iniciam a execução a partir do valor atual do apontador de instruções (PC). Este pode
ser ajustado utilizando-se o comando “Alterar PC” ou “Zerar PC” (F10). Para o modo
contínuo, através do comando “Rodar” (F9), voce pode determinar um ponto de parada na
execução do seu programa.

A-4
Alterar AC
permite fornecer um valor inicial para o acumulador (AC). Após o comando, o
simulador solicita um endereço para inicializar o AC.

Alterar PC
permite fornecer um valor inicial para o apontador de instruções (PC). Após o
comando, o simulador solicita um endereço para inicializar o PC.

Passo (F8)
cada vez que o comando é ativado (via menu, tecla F8, ou iconed e passo a passo), o
simulador executa a instrução apontada pelo PC, parando logo após.
BP Break point
permite especificar um endereço de parada. No campo BP da janela de programa
pode ser fornecido o endereço para o break point.
Rodar (F9)
após o comando “Rodar” (via menu, tecla F9 ou icone) o simulador executa
continuamente um programa, a partir da posição indicada pelo PC, até encontrar uma
instrução de halt (HLT), o ponto de parada ou o comando ser ativado novamente
(freio de emergência).

A . 4 . 9 Salvando e carregando arquivos

Para os comandos que seguem, deve ser fornecido um nome de arquivo. Os simuladores
adicionam, automaticamente, o sufixo “.mem”. Arquivos de simuladores diferentes podem
ou não ser compatíveis entre si, dependendo da compatibilidade entre as respectivas
arquiteturas.
Carregar
carrega um arquivo para a memória do computador sendo simulado. O arquivo deve
conter uma memória compatível com o computador sendo simulado, caso contrário o
simulador indica erro e o comando é anulado.
Salvar
grava o conteúdo da memória do computador sendo simulado.

A-5

Anda mungkin juga menyukai