Pular para o conteúdo principal

Matemática com circuitos

Até aqui aprendemos a construir portas lógicas que implementam AND, OR e NOT. Mas portas lógicas sozinhas ainda não parecem com o que imaginamos quando pensamos em "computação". Afinal, um computador soma números, multiplica, compara. Onde fica toda essa matemática?

A resposta é que toda operação matemática que um computador realiza é implementada em circuitos, e esses circuitos são feitos de portas lógicas que acabamos de estudar. Neste capítulo vamos ver como isso acontece na prática, começando pela adição.

Adição binária

Antes de construir qualquer circuito, precisamos entender como a adição funciona em binário.

A boa notícia é que o princípio é o mesmo de qualquer sistema posicional, incluindo o decimal que você já conhece. Pegue como exemplo a soma de 0010 e 0011. Alinha os números e começa pelo bit menos significativo, o que está mais à direita.

Adicao 01

A primeira coluna é simples: 0 + 1 = 1, tranquilo, né?

A segunda coluna já é mais destemida. Lembra da regra do "vai um" quando a soma ultrapassava 9 no sistema decimal? Aqui acontece o mesmo, mas em binário o limite é muito mais baixo, 1 + 1 = 10. O resultado tem dois símbolos, mas a posição atual só comporta um. O que fazemos é registrar o 0 na posição atual e carregar o 1 para a próxima coluna.

Adicao 02

informação

Esse bit carregado para a próxima posição é chamado de carry bit. Vamos usar esse termo daqui em diante no lugar da expressão mais informal "bit que subiu".

A próxima coluna então ficaria 0 + 0 + 1 = 1, onde esse 1 extra é o carry bit que veio da posição anterior.

Half adder - somador parcial

Agora que entendemos como a adição binária funciona no papel, podemos construir um circuito que realiza essa operação.

HalfAddder 01

Para somar dois bits, precisamos de duas entradas, vamos chamá-las de A e B, e o circuito terá duas saídas:

  • S (sum): o resultado da soma
  • CoutC_{out} (carry-out): o carry bit de saída, o "vai um" para a próxima posição

E ele é representado da seguinte forma:

HalfAddder 01

A tabela verdade desse circuito:

ABSCoutC_{out}
0000
0110
1010
1101

Se você olhar com atenção, vai reconhecer dois padrões familiares:

  • A coluna S é idêntica à tabela verdade do XOR
  • A coluna CoutC_{out} é idêntica à tabela verdade do AND

Isso significa que podemos construir esse somador usando essas duas portas em paralelo, com as mesmas entradas A e B alimentando as duas ao mesmo tempo!

HalfAddder 03

Esse circuito tem o nome de half adder (somador parcial). O "parcial" no nome já entrega o spoiler de que ele resolve a adição dos primeiros dois bits, mas não consegue lidar com um carry bit vindo de uma posição anterior. E quando somamos números com mais de um bit, todas as posições além da primeira precisam lidar com esse caso.

Full adder - somador completo

Cada bit além do menos significativo pode receber um carry bit da posição anterior. Chamamos esse bit de CinC_{in} (carry-in). Para incorporar essa terceira entrada, precisamos de um novo circuito chamado full adder.

O full adder tem três entradas, A, B e CinC_{in}, e duas saídas, S e CoutC_{out}:

ABCinC_{in}SCoutC_{out}
00000
00110
01010
01101
10010
10101
11001
11111

Construindo o full adder com dois half adders

Como o próprio nome sugere, um full adder pode ser construído a partir de dois half adders encadeados.

O primeiro half adder (HA1) soma A e B, gerando uma soma parcial e um primeiro CoutC_{out}. O segundo half adder (HA2) soma esse resultado com CinC_{in}, gerando a saída final S e um segundo CoutC_{out}. O CoutC_{out} final do full adder é 1 se qualquer um dos dois half adders gerou carry, o que é exatamente o que uma porta OR faz.

Mais um exemplo de encapsulamento em ação. Uma vez construído, o full adder pode ser usado como um bloco único, sem que quem o utilize precise saber o que há por dentro. Três entradas, duas saídas, e isso é tudo que importa para quem vai usá-lo.

Somador de 4 bits

Com um full adder em mãos, temos estrutura suficiente para ir além de dois bits.

Para somar números de 4 bits, enfileiramos os circuitos de adição de forma que o CoutC_{out} de cada posição vire o CinC_{in} da próxima. A estratégia é usar um half adder para o bit menos significativo, pois não há carry-in nessa posição, e um full adder para cada um dos três bits restantes.

Para ilustrar, considere a soma de 0010 (2) e 0011 (3). O processamento ocorre da direita para a esquerda, com cada somador passando seu carry-out para o próximo:

PosiçãoABCinC_{in}SCoutC_{out}
Bit 001-10
Bit 111001
Bit 200110
Bit 300000

Resultado: 0101 = 5. Correto.

Esse tipo de somador é chamado de ripple carry adder (somador com propagação de carry), porque o carry bit se propaga de posição em posição até o fim. Cada carry que se propaga introduz um pequeno atraso, então aumentar o número de bits torna o somador progressivamente mais lento. Para números maiores, existem arquiteturas mais eficientes, mas a ideia base é exatamente essa.

dica

Chips prontos com somadores de 4 bits estão disponíveis na série 7400 de ICs. Na prática, você não precisa montar um somador a partir de portas individuais se já existe um componente encapsulado para isso.

Números com sinal

Até aqui representamos apenas números positivos. Mas computadores precisam lidar com números negativos também, e o problema é que um bit só pode ser 0 ou 1, e um sinal negativo não é nenhum dos dois.

Abordagem de sinal de magnitude

A primeira ideia que vem à cabeça é reservar o bit mais significativo para o sinal, com 0 para positivo, 1 para negativo. O restante dos bits representa o valor absoluto. Funciona conceitualmente, mas exige que o somador seja modificado para entender esse bit de sinal, o que complica o hardware sem necessidade.

Abordagem complemento de dois

Existe uma solução melhor chamada complemento de dois (two's complement). A ideia é representar negativos de forma que o somador que já construímos continue funcionando sem nenhuma modificação.

O processo para converter um número positivo em negativo é o seguinte:

PassoProcesso
Escreva o número que positivo em binário primeiro5 = 0101
Inverta todos os bits0101 -> 1010
Some 1 ao resultado1010 + 1 = 1011

Logo, -5 em complemento de dois é 1011. E o processo é reversível, basta aplicar os mesmos três passos de volta retorna ao valor original.

Uma parte divertida é que para fazer uma subtração basta fazer uma adição de um número positivo com um negativo. Por exemplo, para calcular 7 + (-3):

  0111   (7)
+ 1101 (-3 em complemento de dois)
------
10100

O bit que extrapola os 4 bits é descartado, e o resultado é 0100 = 4 que é exatamente o resultado esperado para 7 - 3.

Para interpretar um número em complemento de dois, basta lembrar que o bit mais significativo tem peso negativo. Para 4 bits, os pesos das posições são:

-8  4  2  1

Então -3 seria representado como:

-8  4  2  1
1 1 0 1

As posições "ativas" são -8, 4 e 1, portanto: 8+4+1=3-8 + 4 + 1 = -3.

Então sempre que o bit mais significativo for 1, o número é negativo e quando for 0, é positivo. Ao usar o bit mais significativo para indicar o sinal, perdemos uma posição que antes contribuía para a magnitude. Veja a tabela abaixo onde demonstro a nova distribuição.

Sem sinal (unsigned)Com sinal (signed)
Mínimo0-8
Máximo157
Total de valores1616

A distribuição é assimétrica porque o zero ocupa uma posição do lado positivo (0000), então o lado negativo alcança -8 enquanto o positivo só chega até 7. A fórmula geral para nn bits com sinal:

maˊximo=2n11mıˊnimo=2n1total=2n\text{máximo} = 2^{n-1} - 1 \qquad \text{mínimo} = -2^{n-1} \qquad \text{total} = 2^n

Observe um exemplo com 8 bits:

maˊximo=2811mıˊnimo=281total=28maˊximo=271mıˊnimo=27total=28maˊximo=127mıˊnimo=128total=256\text{máximo} = 2^{8-1} - 1 \qquad \text{mínimo} = -2^{8-1} \qquad \text{total} = 2^8 \newline \text{máximo} = 2^7 - 1 \qquad \text{mínimo} = -2^7 \qquad \text{total} = 2^8 \newline \text{máximo} = 127 \qquad \text{mínimo} = -128 \qquad \text{total} = 256

Quando se sabe que negativos nunca serão necessários, faz sentido usar unsigned e recuperar toda a faixa para valores positivos:

BitsSignedUnsigned
4 bits-8 a 70 a 15
8 bits-128 a 1270 a 255
16 bits-32.768 a 32.7670 a 65.535