Beruflich Dokumente
Kultur Dokumente
de Computadores
Representação de Dados
Arquitetura e Organização
Noções de Software Básico
Notas de aula
iii
SOBRE O AUTOR
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
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
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
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
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.
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:
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
• 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.
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
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 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:
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
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
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
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.
A divisão seguinte por B produz como resto x1, e assim sucessivamente até xn-1.
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
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:
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.
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”).
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.
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).
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.
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
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.
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)=‘+’.
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
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:
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.
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.
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.
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
2-8
2 . 3 . 4 Números com sinal: 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.
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.
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.
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.
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-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
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
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).
2 . 7 Exercícios propostos
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?
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.
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).
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.
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.
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.
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
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
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.
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.
memória
unidade
controle operacional
entrada/
saída
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.
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
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.
operandos
códigos de
controle condição
U L A
resultado
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.
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.
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.
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).
RI unidade
de sinais de
controle controle
RST
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 )
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.
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.
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 ).
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
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
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.
A1 A2 A3 A4 C
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.
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.
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
instruções
e dados
IBR PC
memória
principal
IR AR
circuitos de
endereços
: sinais de
controle :
controle
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.
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
↑
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:
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:
• 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;
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.
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.
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
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
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.
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.
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
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:
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
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.
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+*+–//
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
4 . 1 Características
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.
memória
endereç
operando
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):
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
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):
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.
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
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:
4 . 7 Conclusão
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-4
em 8 bits, deve aparecer na primeira posição livre e overflow deve ser indicado da
seguinte forma:
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
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
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.
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.
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
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”
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.
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:
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)
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)
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:
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.
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:
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
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
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
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.
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
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
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:
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:
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
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.
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.
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.
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.
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.
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).
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.
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.
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.
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.
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.
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.
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
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
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
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)
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.
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.
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.
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
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.
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.
7-4
total de bits da mantissa e do expoente) pode ser representada. Os números estão então
dispersos dentro desta faixa.
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).
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):
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).
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.
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).
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).
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
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.
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.
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:
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:
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
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.
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:
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 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.
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
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
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.
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
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).
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
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.
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
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.
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.
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.
a s
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
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
a
s
b
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
a a’
a’+b’
a+b(a’+b’)
b b’
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:
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
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.
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:
O procedimento para a formação de um produto de soma é análogo, conforme pode ser visto
no procedimento abaixo:
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
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.
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.
Extraindo-se da Tabela 9.13 a equação booleana através de uma soma de produtos, obtém-
se:
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
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.
a a
s s
b b
sel sel
a 8 a 8
b 8 b 8
8 s 8 s
c 8 c 8
d 8 d 8
sel 2 sel 2
9-9
saída 0
entrada 1
saída 1
entrada 0
saída 2
saída 3
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’
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
D
Q
Q’
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’
9-13
Dn-1 Dn-2 ....... D1 D0
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.
C S
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.
C
Q0 Q1 ....... Qn-2 Qn-1
Figura 9.21 - Contador binário de n bits
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
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)
A C
Meio Co
Somador
B S C
Meio
Somador
Ci S S
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.
Ci=0
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.
Ci=0
Somador
not(A)
A+B A and B A or B
2
Sel
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.
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
9-18
Capítulo
DEZ
Organização do Neander
1 0 . 1 Elementos necessários
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 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 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.
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
Unidade de Controle
Cód. Op.
DECOD.
P
C
Incrementa PC
N Z
carga NZ
Y
UAL
X
seleção UAL
carga AC
AC
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.
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
10-8
Capítulo
ONZE
Noções de Entrada e Saída
1 1 . 1 Introduçã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).
1 1 . 3 Memória secundária
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.
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.
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
Instruções de E/S
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).
1 2 . 2 Linguagens de programação
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:
12-2
end while
End Program
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
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
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:
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 :
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
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:
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
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:
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
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.
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.
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”.
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.
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.
• 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:
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.
• 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.
• 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.
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.
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;
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”.
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.
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:
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;
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.
• 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.
2
Apêndice
UM
Utilização dos simuladores e depuradores
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).
• 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).
• Menu de comandos
mostra os comandos aceitos pelo simulador/depurador. O caractere sublinhado em cada
comando pode ser usado para ativação via teclado.
• 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.
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.
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):
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.
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 . 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).
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