flowchart TB
BITS["Padrao de 8 bits<br/>1011 0101"] --> HEX["Leitura hexadecimal<br/>agrupa 4 a 4"]
BITS --> UNS["Leitura sem sinal<br/>soma das potencias de 2"]
BITS --> C2["Leitura complemento de dois<br/>bit 7 de peso negativo"]
C2 --> FLAG["Relaciona com o flag N<br/>do registrador STATUS"]
Exercícios do Módulo 02: Representação de Dados e Aritmética Computacional
Os três exercícios a seguir foram montados para você fixar, em dificuldade crescente, o que estudamos esta semana: a aritmética modular que une o inteiro sem sinal ao complemento de dois, a distinção entre carry-out e overflow nos flags do registrador STATUS do PIC18F4550, e o porquê de 0{,}1 + 0{,}2 nunca dar 0{,}3 no IEEE 754. O primeiro pede que você converta e raciocine sobre padrões de bits; o segundo exige a conta dos flags C e OV em uma soma de 16 bits encadeada; o terceiro liga a representação de ponto flutuante à decisão de engenharia que você vai tomar no Projeto Integrador, quando precisar escolher como armazenar uma grandeza física no chip. Resolva cada um com lápis, papel e o datasheet aberto antes de comparar com colegas — quem só lê resolução pronta nunca desenvolve a intuição de prever, no papel, qual flag o silício vai erguer.
Exercício 1: Lendo Padrões de Bits nas Três Leituras Possíveis
Nível Básico
Quero começar pelo gesto mais elementar e, ao mesmo tempo, o mais traído pela pressa: olhar para oito bits dentro de um registrador do PIC18F4550 e dizer que número eles representam. A resposta depende de qual convenção você combinou consigo mesmo antes de ler, porque o silício não distingue: a mesma sequência de bits é, simultaneamente, um inteiro sem sinal, um inteiro em complemento de dois e um par de dígitos hexadecimais. O hardware guarda o padrão; quem escolhe a leitura é você.
Pegue o byte \texttt{1011 0101}_2, que poderia estar agora mesmo no registrador WREG depois de um MOVLW. Este exercício parece bobo até você tentar a primeira vez sem decorar tabelas — eu mesmo já troquei o sinal de um valor em complemento de dois por ter pulado a etapa de inverter e somar um. A ideia é você reconstruir as três leituras à mão, mostrando cada passo, e não apenas anunciar o número final.
O que peço de você: (a) escreva esse byte em hexadecimal, agrupando os bits de quatro em quatro como fazemos para preencher uma listagem do MPLAB X; (b) interprete o padrão como inteiro sem sinal, mostrando a soma das contribuições posicionais \sum d_i \cdot 2^i; (c) interprete o mesmo padrão como inteiro em complemento de dois, usando tanto o atalho de subtrair 2^n do valor sem sinal quanto a regra formal do bit de sinal de peso negativo, N = -b_{7}\cdot 2^{7} + \sum_{i=0}^{6} b_i \cdot 2^i, e confira que os dois caminhos dão o mesmo resultado; e (d) explique, em duas ou três frases, por que o registrador STATUS levantaria o flag N ao receber esse byte como resultado de uma operação, e o que isso tem a ver com a leitura em complemento de dois que você acabou de fazer.
O diagrama abaixo mostra as três rotas de leitura que partem de um único padrão de bits e é o roteiro que você vai seguir, uma rota de cada vez:
Você terá terminado quando tiver, lado a lado, o valor hexadecimal, o valor sem sinal e o valor em complemento de dois desse mesmo byte, com as contas à mostra, e quando conseguir dizer em uma frase por que o flag N se acende para esse padrão.
Exercício 2: Carry-out Contra Overflow numa Soma de 16 Bits
Nível Intermediário
Agora vamos para a confusão que mais derruba quem estudou complemento de dois por cima: tratar carry-out e overflow como se fossem a mesma coisa. Eles não são, e a maneira de gravar a diferença na memória é fazer a conta sentindo o PIC18F4550 trabalhar. Lembre que esse chip tem ULA de 8 bits, então uma soma de uint16_t ou int16_t é construída em duas etapas: o byte baixo entra pela instrução ADDWF, que atualiza o flag C do STATUS, e o byte alto entra pela ADDWFC, que soma os dois bytes altos somando também o C herdado da etapa anterior. O flag OV medido depois da segunda instrução é o overflow da soma de 16 bits inteira.
Considere a soma \texttt{0x7FFF} + \texttt{0x0001}, com os dois operandos tratados como int16_t em complemento de dois. Lembre que o flag C recebe o carry-out do bit mais significativo na leitura sem sinal, enquanto o flag OV sinaliza que o resultado em complemento de dois saiu da faixa [-32768, +32767]. A regra booleana do overflow, que vimos no material, é \text{overflow} = (s_a \oplus s_r) \wedge (s_b \oplus s_r), onde s_a, s_b e s_r são os bits de sinal dos dois operandos e do resultado.
O que peço de você: (a) realize a soma byte a byte, mostrando primeiro a soma dos bytes baixos com o C que ela gera, depois a soma dos bytes altos pela ADDWFC incluindo esse C, e apresente o padrão de 16 bits do resultado; (b) leia o resultado nas duas convenções, sem sinal e complemento de dois, e diga qual delas produz um valor matematicamente errado em relação à soma pretendida; (c) determine o estado dos flags C e OV ao final, aplicando a expressão booleana do overflow aos bits de sinal, e explique em palavras por que, neste caso específico, OV = 1 enquanto C = 0; e (d) repita o raciocínio completo para a soma \texttt{0xFFFF} + \texttt{0x0001} e contraste os dois resultados numa frase que explique por que carry-out e overflow são condições independentes — uma vale para a leitura sem sinal, a outra para a leitura com sinal.
O diagrama abaixo resume o encadeamento das duas instruções que constroem a soma de 16 bits e onde cada flag é observado:
flowchart TB
LOW["Bytes baixos<br/>somados por ADDWF"] --> CARRY["Gera carry C<br/>do bit 7 baixo"]
CARRY --> HIGH["Bytes altos<br/>somados por ADDWFC + C"]
HIGH --> RES["Resultado de 16 bits"]
RES --> CFLAG["Flag C<br/>leitura sem sinal"]
RES --> OVFLAG["Flag OV<br/>leitura complemento de dois"]
Você terá terminado quando tiver, para as duas somas, o padrão de bits do resultado, as duas leituras numéricas, o estado dos flags C e OV com a conta da expressão booleana à mostra, e uma frase justificando por que um flag pode estar aceso enquanto o outro está apagado.
Exercício 3: Decidindo a Representação de uma Grandeza Física no Kit
Nível Desafiador
Chegou a hora de amarrar a teoria do IEEE 754 à decisão de engenharia que você vai enfrentar no Projeto Integrador, quando o seu sistema no PIC18F4550 do kit ACEPIC PRO V8.2 precisar acumular uma grandeza física e exibi-la no display LCD Sunstar 2004A. Imagine que o seu sistema lê, a cada ciclo, uma dose ou uma temperatura em passos de um décimo de unidade, e que essa leitura precisa ser somada milhares de vezes ao longo da operação. Você tem três candidatas de representação na mesa: float em IEEE 754 binário32, int32_t armazenando o valor em décimos de unidade como inteiro, e ponto fixo com fator de escala escolhido por você. A escolha errada compromete a precisão acumulada, o tempo de resposta ou ambos.
Quero que você faça essa análise no papel antes de declarar qualquer variável no código. Há três frentes que separam uma escolha defensável de um chute, e elas são o coração deste exercício. A primeira é a precisão: você já sabe que 0{,}1 é dízima periódica em binário e que o IEEE 754 a arredonda para o representável mais próximo, com erro da ordem de 2^{-24} por valor; some isso milhares de vezes e o erro de arredondamento acumula. A segunda é o custo: o PIC18F4550 não tem unidade de ponto flutuante, e cada soma em float é emulada em software pela biblioteca de runtime do XC8, custando muito mais ciclos do que uma soma inteira encadeada com ADDWF e ADDWFC. A terceira é a faixa dinâmica: você precisa saber se a grandeza varia poucas ordens de magnitude, caso em que o inteiro escalado basta, ou muitas, caso em que a precisão relativa constante do ponto flutuante passa a valer o preço.
O que peço de você: (a) explique, em prosa e com a fórmula N = (-1)^s \cdot (1 + F \cdot 2^{-23}) \cdot 2^{E-127} na mão, por que o valor 0{,}1 não tem representação exata em binário32 e o que acontece com o erro quando você soma esse valor arredondado milhares de vezes; (b) compare quantitativamente, em termos de precisão e de custo em ciclos, a soma de duas grandezas usando float contra a soma usando int32_t em décimos de unidade, lembrando que a soma inteira de 32 bits no PIC18 é construída encadeando quatro somas de 8 bits; (c) decida, justificando com faixa dinâmica, precisão relativa e custo computacional, qual das três representações você adotaria para acumular essa grandeza no seu sistema, e em que situação hipotética a sua escolha mudaria; e (d) descreva como você confirmaria empiricamente a imprecisão do float no próprio kit, inspecionando no simulador do MPLAB X os quatro bytes IEEE 754 de 0{,}1, 0{,}2, 0{,}3 e 0{,}1+0{,}2 e exibindo o resultado no LCD — conectando isso à terceira tarefa do projeto deste módulo, em que você evidencia casos de imprecisão de ponto flutuante rodando no microcontrolador.
O fluxo abaixo organiza o raciocínio de decisão que vou cobrar na parte (c), partindo das três frentes de análise e desembocando na escolha justificada:
flowchart TB
GRAND["Grandeza fisica<br/>acumulada milhares de vezes"] --> PREC["Precisao<br/>erro de arredondamento acumula?"]
GRAND --> CUSTO["Custo em ciclos<br/>float emulado vs inteiro encadeado"]
GRAND --> FAIXA["Faixa dinamica<br/>poucas ou muitas ordens de magnitude?"]
PREC --> ESCOLHA["Representacao escolhida<br/>float, int32 escalado ou ponto fixo"]
CUSTO --> ESCOLHA
FAIXA --> ESCOLHA
ESCOLHA --> LCD["Confirma no kit<br/>bytes IEEE 754 e exibe no LCD"]
Quando você conseguir defender a sua escolha de representação com os três argumentos amarrados — precisão acumulada, custo em ciclos no PIC18 e faixa dinâmica da grandeza — e descrever um experimento no MPLAB X que torne visível a imprecisão do float, você terá feito exatamente o tipo de análise que separa o programador embarcado consciente daquele que declara float por hábito. Esse é o critério de pronto deste exercício, e a justificativa importa mais do que o veredito.
Orientações para Resolução
O propósito destes exercícios não é só chegar ao número certo, mas treinar seu raciocínio sobre a única ideia que costura o módulo inteiro: o silício guarda padrões de bits, e o significado depende da convenção que você adota ao lê-los. Antes de calcular qualquer coisa, releia, no material do módulo, as seções sobre o anel dos inteiros módulo 2^n, a regra de overflow em complemento de dois, os cinco flags do registrador STATUS e o layout do IEEE 754.
Para o exercício básico, faça as três leituras à mão sem decorar resultados — é o traçado das contas posicionais que fixa a diferença entre as convenções. Para o intermediário, escreva a soma byte a byte com os flags ao lado de cada etapa, e resista à tentação de declarar C e OV iguais antes de aplicar a expressão booleana. Para o desafiador, trate a decisão de representação como um argumento de engenharia, não como preferência: descreva o experimento no MPLAB X com rigor suficiente para que sua dupla o reproduza e chegue à mesma conclusão.
Discuta suas soluções com os colegas do seu grupo e traga suas dúvidas para a aula teórica, onde vamos comparar as leituras de cada um e observar, no simulador, os flags se acenderem.