Exercícios do Módulo 04: Unidade Central de Processamento — Caminho de Dados

Os três exercícios a seguir foram pensados para você fixar, em ordem crescente de dificuldade, o que estudamos sobre o caminho de dados do seu PIC18F4550: a ALU combinacional e seus flags, o banco assimétrico com WREG e File Register, os multiplexadores e barramentos, as fases do ciclo de instrução e a álgebra de ciclos e CPI. O primeiro pede que você raciocine sobre os flags do registrador STATUS; o segundo exige que você rastreie o fluxo de dados e calcule ciclos de um trecho real; o terceiro liga toda a teoria à medição de tempos que você vai gravar no kit ACEPIC PRO V8.2 no Projeto Integrador. Resolva cada um por conta própria antes de comparar com colegas — quem só lê a resolução pronta nunca desenvolve a intuição de prever, no papel, qual flag muda e em qual estado Q cada coisa acontece.

Exercício 1: Por Que o AND Não Mexe no Carry

Nível Básico

Eu quero começar pela ALU do seu PIC18F4550, porque é nela que mora uma confusão que vejo todo semestre: a ideia errada de que toda operação atualiza todos os flags do registrador STATUS. No PIC18F4550 o STATUS guarda cinco flags — Z (zero), C (carry), DC (digit carry), OV (overflow em complemento de dois) e N (negativo) — e cada classe de operação mexe apenas em um subconjunto deles, conforme o que faz sentido para aquela operação. Este exercício parece óbvio até você tentar justificar a regra olhando para a estrutura física da ALU, e não para uma tabela decorada.

Imagine que, no MPLAB X, você gravou no WREG o valor 0x80 e em um registrador DADO do Access Bank o valor 0x80. Em seguida você executa três instruções, uma de cada vez, capturando o STATUS imediatamente após cada uma: primeiro uma soma ADDWF DADO, W, A, depois um ANDWF DADO, W, A e por fim um RLNCF DADO, W, A, que é um deslocamento à esquerda sem passar pelo carry. Lembre que a ALU é puramente combinacional: ela recebe os dois operandos e um vetor de sinais de controle, e um multiplexador de saída roteia os flags conforme a operação realmente realizada.

O que peço de você: (a) para a soma de 0x80 com 0x80, diga quais dos cinco flags são alterados e qual valor lógico cada um recebe, justificando especialmente por que C, OV e Z assumem o valor que assumem; (b) para o ANDWF, explique por que somente Z e N podem mudar e por que C, DC e OV permanecem com o valor que tinham antes — sua justificativa deve apoiar-se no fato de a operação lógica não envolver o somador; (c) descreva, em duas ou três frases, o que aconteceria com a sua lógica de programa se você assumisse, por engano, que o ANDWF zera o carry, e em seguida executasse um ADDWFC (soma com carry) contando com esse zero; e (d) explique por que essa regra de roteamento seletivo de flags é uma decisão de organização, e não de arquitetura, retomando a distinção que vimos no início do módulo.

O diagrama abaixo mostra, de forma esquemática, por que o multiplexador de flags entrega valores diferentes conforme a operação — é esse roteamento que você vai usar para justificar suas respostas:

flowchart TB
    OP["Operacao selecionada<br/>pelo controle"] --> SOMA["Subcircuito somador<br/>produz C, DC, OV"]
    OP --> LOGI["Subcircuito logico<br/>nao produz C, DC, OV"]
    SOMA --> MUXF["MUX de flags"]
    LOGI --> MUXF
    ZN["Z e N derivam<br/>sempre do resultado"] --> MUXF
    MUXF --> STATUS["STATUS<br/>flags atualizados ou preservados"]

Você terá terminado quando conseguir dizer, para cada uma das três instruções, exatamente quais flags mudam e quais ficam intactos, e quando souber explicar essa diferença olhando para qual subcircuito da ALU foi acionado — não para uma regra memorizada.


Exercício 2: Rastreando o Fluxo de Dados e Contando os Ciclos de um Laço

Nível Intermediário

Agora vamos juntar duas coisas que estudamos separadamente: o percurso físico que um operando faz pelo caminho de dados e a contagem de ciclos que esse percurso custa. Trabalhe com o PIC18F4550 do seu kit operando com oscilador de 8 MHz, de modo que o ciclo de máquina vale T_{cm} = 4/f_{osc} = 0{,}5\ \mu\text{s}. Lembre da tabela de ciclos do módulo: instruções aritméticas e lógicas registrador-registrador no Access Bank custam um ciclo de máquina; um desvio condicional custa um ciclo quando não é tomado e dois quando é tomado, porque a instrução pré-buscada pelo prefetch é descartada e uma bolha aparece.

Considere uma instrução ADDWF DADO, F, A, que soma WREG ao registrador DADO no Access Bank e grava o resultado de volta em DADO. Quero primeiro que você rastreie esse operando pelo caminho de dados: por onde entra DADO, por onde entra WREG, qual entrada da ALU recebe cada um, qual multiplexador decide que o destino é o próprio DADO e não WREG, e em qual dos quatro estados Q do ciclo de máquina cada uma dessas coisas acontece. Em seguida, considere um laço que percorre um vetor de oito posições. A cada iteração o laço executa quatro instruções monociclo de processamento mais um desvio condicional ao topo, que é tomado em todas as iterações menos a última.

O que peço de você: (a) descreva em prosa o percurso completo do ADDWF DADO, F, A pelo caminho de dados, nomeando o banco, a ALU, o multiplexador de destino e associando cada etapa a um dos estados Q1 a Q4 que vimos no módulo; (b) explique por que essa instrução, embora leia um operando, opere na ALU e escreva o resultado, ainda assim cabe em um único ciclo de máquina, recorrendo à sobreposição entre execução e prefetch da arquitetura Harvard; (c) calcule o número total de ciclos de máquina do laço completo de oito iterações, separando claramente os ciclos das instruções de processamento dos ciclos do desvio tomado e do único desvio não tomado, e converta esse total para microssegundos a 8 MHz; e (d) calcule o CPI efetivo do laço, mostrando a conta \text{CPI}_{ef} = \text{ciclos}/\text{instruções}, e explique por que esse valor fica acima de 1,0 mesmo todas as instruções de processamento sendo monociclo.

O diagrama abaixo resume o caminho que o operando percorre na instrução do item (a) e que você deve descrever com seus próprios termos:

flowchart LR
    WREG["WREG"] --> ALUA["Entrada A da ALU"]
    FR["File Register<br/>DADO no Access Bank"] --> ALUB["Entrada B da ALU"]
    ALUA --> ALU["ALU<br/>soma"]
    ALUB --> ALU
    ALU --> MUXD["MUX de destino<br/>d seleciona F"]
    MUXD --> FRW["Escreve de volta<br/>em DADO"]
    ALU --> ST["Atualiza STATUS"]

Você terá terminado quando tiver, em números com unidades, o total de ciclos do laço, o tempo em microssegundos a 8 MHz e o CPI efetivo, além de uma descrição do percurso do operando que associe cada passo a um estado Q.


Exercício 3: Prevendo e Medindo Ciclos de Instrução com o Timer0 no Kit

Nível Desafiador

Chegou a hora de amarrar a teoria do ciclo de instrução à segunda e à terceira tarefas do Projeto Integrador deste módulo, em que você vai gravar no PIC18F4550 do kit ACEPIC PRO V8.2 um experimento que mede o número de ciclos de máquina de diferentes classes de instrução e confronta a medição com a previsão teórica que tiramos do datasheet. A metodologia usa o Timer0 como cronômetro de ciclos: configurado sem prescaler, ou seja com prescaler 1:1, o Timer0 incrementa exatamente uma vez por ciclo de máquina, e a diferença entre a leitura final e a inicial é o número exato de ciclos consumidos pelo trecho cronometrado. O resultado da contagem você vai exibir nos bits baixos do PORTD, observando os LEDs do kit acenderem conforme o padrão binário do número medido.

Quero que você projete, no papel, a previsão teórica e o procedimento de medição, antes de escrever uma linha de código. Há três armadilhas de método que separam uma medição confiável de uma enganosa. A primeira é a leitura do contador de dezesseis bits do Timer0, dividido em TMR0L (byte baixo) e TMR0H (byte alto): o hardware só trava o byte alto em um registrador-tampão no instante em que o byte baixo é lido, de modo que ler na ordem errada pode capturar um valor inconsistente quando o contador cruza um múltiplo de 256. A segunda é o sobrecusto da própria instrumentação — as instruções que zeram e leem o Timer0 também consomem ciclos, e esse custo fixo precisa ser medido em torno de um trecho vazio e depois subtraído, exatamente como descontamos a tara do recipiente antes de pesar a substância. A terceira é repetir o trecho um número grande e conhecido de vezes e dividir a contagem total por esse número.

O que peço de você: (a) usando a tabela de ciclos por classe do módulo, preveja no papel quantos ciclos de máquina você espera medir para uma instrução aritmética monociclo no Access Bank, para um desvio condicional tomado e para um par CALL/RETURN de uma sub-rotina vazia, justificando cada número pela sobreposição com o prefetch e pelas bolhas; (b) explique a ordem correta de leitura entre TMR0L e TMR0H e por que a ordem inversa pode produzir uma contagem errada — descreva o raciocínio, não escreva o código; (c) descreva o procedimento completo de medição de uma classe de instrução, incluindo como você isola e desconta o sobrecusto da instrumentação e como a repetição de N vezes reduz o efeito desse sobrecusto sobre o ciclo médio medido; e (d) descreva como você levaria a contagem de ciclos para os bits baixos do PORTD e o que o padrão de LEDs acesos significaria, conectando isso à tarefa do projeto de construir uma tabela de ciclos por classe com metodologia documentada e à tarefa de evidenciar as fases do ciclo de instrução.

Para guiar a parte (c), siga o fluxo de medição esquematizado abaixo, que separa a calibração do sobrecusto da medição da instrução real:

flowchart TB
    START["Zera Timer0<br/>prescaler 1:1"] --> CALIB["Cronometra trecho vazio<br/>repetido N vezes"]
    CALIB --> OVER["Obtem sobrecusto fixo<br/>da instrumentacao"]
    OVER --> MEAS["Cronometra a classe de instrucao<br/>repetida N vezes"]
    MEAS --> SUB["Subtrai o sobrecusto<br/>e divide por N"]
    SUB --> CICLO["Ciclos medidos por instrucao"]
    CICLO --> CONFR["Confronta com a previsao<br/>tirada da tabela do datasheet"]
    CONFR --> PORTD["Exibe a contagem<br/>nos LEDs do PORTD"]

Quando os ciclos que você mediu no Timer0 coincidirem, dentro de uma margem pequena, com os ciclos que você previu a partir da tabela de classes, você terá evidência empírica de que entendeu o modelo de temporização do caminho de dados — e esse é exatamente o critério de pronto deste exercício. Se eles divergirem, investigar a causa da divergência, seja um sobrecusto mal descontado, seja uma bolha de desvio não prevista, vai te ensinar mais sobre o ciclo de instrução do que a coincidência ensinaria, e essa investigação é parte legítima do entregável.


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 como o caminho de dados transforma bits em resultado dentro do orçamento apertado de quatro pulsos do oscilador por ciclo de máquina. Antes de resolver qualquer coisa, releia, no material do módulo, as seções sobre a ALU e os flags, o banco assimétrico com WREG e File Register, as fases do ciclo de instrução e os quatro estados Q, e a análise quantitativa via ciclos e CPI.

Para o exercício básico, resista à tentação de decorar uma tabela de flags e justifique cada atualização olhando para qual subcircuito da ALU foi acionado. Para o intermediário, escreva todos os passos da conta de ciclos com unidades, sem pular etapas, e confira se o CPI que você obteve faz sentido em relação ao piso de um ciclo por instrução. Para o desafiador, trate a previsão teórica e o método de medição como duas peças que precisam encaixar: descreva o procedimento com rigor suficiente para que outra pessoa o reproduza no kit e chegue ao mesmo resultado.

Discuta suas soluções com os colegas do seu grupo e traga suas dúvidas para a aula teórica, onde vamos comparar as previsões de cada um com as medições reais no Timer0.