Módulo 4: Unidade Central de Processamento — O Caminho de Dados

Seja bem-vindo ao quarto módulo! Até aqui, você aprendeu o que são arquiteturas de computadores, como os dados são representados em bits, e quais instruções o PIC18F4550 consegue executar. Agora chegou o momento mais esperado: abrir a CPU por dentro. Neste módulo você vai descobrir exatamente como os componentes físicos do processador trabalham em conjunto para executar cada instrução que você escreveu nos módulos anteriores. Se você já se perguntou “mas o que acontece de verdade lá dentro quando o processador soma dois números?”, a resposta começa aqui.

Por Que Entender o Caminho de Dados Muda Tudo

Imagine que você está depurando um programa no MPLAB X e, ao executar passo a passo, percebe que uma variável assume um valor inesperado. Você sabe que o compilador gerou a instrução ADDWF e que ela deveria somar dois valores — mas de onde vêm esses valores? Para onde vai o resultado? Por que certos flags de status mudam e outros não? Sem entender o caminho de dados, você está essencialmente trabalhando no escuro.

O caminho de dados (datapath) é o conjunto de componentes físicos do processador responsável por movimentar e transformar informação durante a execução das instruções. Ele é composto pela Unidade Lógica e Aritmética, pelo banco de registradores, pelos barramentos de dados e pelos multiplexadores que selecionam as rotas corretas a cada ciclo de clock. Quando você entende esses componentes e como eles se interconectam, o comportamento do processador deixa de ser misterioso e passa a ser completamente previsível.

Além do aspecto prático, este módulo representa uma virada conceitual importante no seu aprendizado: você passará a enxergar o processador não como uma “caixa preta” mágica, mas como um sistema de engenharia com partes bem definidas, cada uma com função específica. Essa perspectiva é exatamente a que engenheiros e arquitetos de sistemas utilizam para projetar, otimizar e depurar processadores reais.

No Projeto Integrador, o conhecimento deste módulo será aplicado de forma imediata: você irá analisar o uso dos registradores no código que seu grupo já desenvolveu, medir o tempo de execução de diferentes classes de instruções e otimizar seções críticas do código para aproveitar melhor o caminho de dados do PIC18F4550. Ao final da sessão de tutoria, você terá uma compreensão profunda de como cada linha de código impacta os componentes físicos do seu microcontrolador.

O diagrama abaixo mostra como os temas deste módulo se relacionam entre si e com os módulos anteriores e posteriores:

flowchart TD
    M3["Módulo 3<br/>ISA e Modos de<br/>Endereçamento"]
    M2["Módulo 2<br/>Representação<br/>de Dados"]
    
    A["Visão Geral do<br/>Caminho de Dados"]
    B["Unidade Lógica e<br/>Aritmética (ULA)"]
    C["Banco de<br/>Registradores"]
    D["Barramentos e<br/>Multiplexadores"]
    E["Ciclo de Instrução<br/>(Fetch–Decode–Execute–WB)"]
    F["Caminho de Dados<br/>Monociclo"]
    G["Registrador STATUS<br/>e Flags"]
    
    PI["Projeto Integrador<br/>Otimização de Registradores<br/>Medição de Ciclos"]
    M5["Módulo 5<br/>Unidade de Controle"]
    M6["Módulo 6<br/>Pipeline"]

    M3 --> A
    M2 --> B
    A --> B
    A --> C
    A --> D
    B --> G
    C --> D
    D --> E
    E --> F
    G --> E
    F --> PI
    F -.->|"próximo módulo"| M5
    M5 -.->|"dois módulos"| M6


O Que É o Caminho de Dados, Afinal?

Para entender o caminho de dados, parta de uma analogia simples. Imagine uma fábrica que produz peças metálicas. Nessa fábrica há máquinas (que realizam operações como cortar, dobrar e soldar), esteiras rolantes (que transportam as peças entre as máquinas) e depósitos temporários onde as peças aguardam enquanto a próxima etapa está ocupada. O caminho de dados de um processador funciona de forma análoga: a ULA é a máquina que transforma os dados, os registradores são os depósitos de alta velocidade onde os dados ficam enquanto são processados, e os barramentos são as esteiras que transportam os bits de um componente para outro.

A diferença crucial em relação a uma fábrica real é a velocidade: no PIC18F4550 rodando a 48 MHz com seu pipeline de dois estágios, a maioria das instruções conclui em apenas um ciclo de máquina — quatro ciclos de clock, ou seja, aproximadamente 83 nanossegundos. Em processadores modernos, essa velocidade é centenas de vezes maior. Mas a estrutura fundamental — operação, armazenamento e transporte — é a mesma.

Definição: Caminho de Dados (Datapath)

O caminho de dados (datapath) é o conjunto de componentes de hardware responsáveis pela movimentação e processamento de dados dentro do processador durante a execução de instruções. Ele inclui:

  • A Unidade Lógica e Aritmética (ULA), que executa operações computacionais
  • O banco de registradores, que fornece armazenamento temporário de alta velocidade
  • Os barramentos internos, que transportam dados entre componentes
  • Os multiplexadores, que selecionam qual fonte de dado é conectada a qual destino em cada ciclo
  • Os registradores de controle como o Program Counter (PC) e o Stack Pointer (SP)

O caminho de dados é complementado pela unidade de controle, que você estudará no próximo módulo. Enquanto o caminho de dados “faz o trabalho”, a unidade de controle “diz o que fazer”.

Uma distinção importante que você já encontrou no Módulo 1 torna-se muito concreta agora: a diferença entre arquitetura e organização. A ISA (que você estudou no Módulo 3) define o que o processador faz na perspectiva do programador — quais registradores existem, quais instruções estão disponíveis, como os operandos são especificados. O caminho de dados é a organização — a implementação física concreta que realiza o que a ISA promete. Dois processadores com a mesma ISA podem ter caminhos de dados radicalmente diferentes, com desempenhos igualmente diferentes.


A Unidade Lógica e Aritmética: O Coração Computacional

A Unidade Lógica e Aritmética — mais conhecida pela sigla ULA, ou ALU em inglês (Arithmetic Logic Unit) — é o componente do processador responsável por executar todas as operações computacionais. Quando o PIC18F4550 soma dois bytes, aplica uma máscara AND a um registrador, ou verifica se um valor é zero, é a ULA que realiza o trabalho.

Para compreender como a ULA funciona, é preciso dar um passo atrás e entender que ela é construída a partir de componentes ainda mais elementares: as portas lógicas. Você provavelmente já encontrou portas AND, OR e NOT em disciplinas anteriores. A ULA nada mais é do que uma coleção muito bem organizada dessas portas, arranjadas para realizar operações úteis sobre palavras de bits.

Das Portas Lógicas às Operações

Comece pelo caso mais simples: a operação AND bit a bit entre dois registradores de 8 bits. Para realizá-la, a ULA precisa apenas de 8 portas AND independentes, cada uma recebendo um bit correspondente dos dois operandos e produzindo um bit do resultado. Isso é o que chamamos de circuito combinacional: a saída depende apenas dos valores atuais das entradas, sem nenhuma memória do passado.

flowchart LR
    subgraph ENTRADA["Operandos (8 bits cada)"]
        A["A = 10110101"]
        B["B = 00001111"]
    end
    subgraph AND8["ULA: AND de 8 bits"]
        G0["AND"] 
        G1["AND"]
        G2["AND"]
        G3["AND"]
        G4["AND"]
        G5["AND"]
        G6["AND"]
        G7["AND"]
    end
    subgraph SAIDA["Resultado"]
        R["R = 00000101"]
    end
    A --> AND8
    B --> AND8
    AND8 --> SAIDA

A operação OR de 8 bits é igualmente simples: troca-se as 8 portas AND por 8 portas OR. A operação XOR substitui por portas XOR. A operação NOT (complemento) usa 8 portas inversoras. Todas essas operações lógicas têm em comum o fato de serem executadas em paralelo — todos os 8 bits são processados simultaneamente, não um por vez.

A operação aritmética de adição é mais interessante porque introduz o conceito de carry (transporte). Quando você soma dois bits que resultam em 1 + 1 = 10 em binário, o resultado tem dois bits: o 0 fica na posição atual e o 1 “transporta” para a posição seguinte. Isso é exatamente o que acontece quando você soma números de múltiplos dígitos à mão: se a soma de uma coluna ultrapassa 9, você anota o excesso e “vai um” para a próxima coluna.

Exemplo: O Somador Completo de 1 Bit

O somador completo (full adder) é o bloco fundamental da adição binária. Ele recebe três entradas — o bit A, o bit B e o carry de entrada (Cin) — e produz duas saídas: a soma S e o carry de saída (Cout).

A tabela verdade completa é:

A B Cin S Cout
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

As equações lógicas derivadas dessa tabela são:

S = A \oplus B \oplus C_{in}

C_{out} = (A \cdot B) + (B \cdot C_{in}) + (A \cdot C_{in})

Onde \oplus representa a operação XOR, \cdot representa AND e + representa OR.

Para construir um somador de 8 bits, você encadeia 8 somadores completos em série: o carry de saída do bit 0 torna-se o carry de entrada do bit 1, e assim por diante. Essa configuração é chamada de somador ripple-carry (carry que se propaga em ondas). O nome é muito descritivo: o carry “ondula” da posição menos significativa até a mais significativa, e o processador precisa esperar que essa propagação se complete antes de ter o resultado correto.

flowchart LR
    A0["A[0]"] --> FA0["Full Adder<br/>Bit 0"]
    B0["B[0]"] --> FA0
    C0["Cin=0"] --> FA0
    FA0 -->|"S[0]"| R0["R[0]"]
    FA0 -->|"Cout"| FA1["Full Adder<br/>Bit 1"]
    
    A1["A[1]"] --> FA1
    B1["B[1]"] --> FA1
    FA1 -->|"S[1]"| R1["R[1]"]
    FA1 -->|"Cout"| FA2["Full Adder<br/>Bit 2"]
    
    A2["A[2]"] --> FA2
    B2["B[2]"] --> FA2
    FA2 -->|"S[2]"| R2["R[2]"]
    FA2 -->|"Cout..."| FAN["...até bit 7"]
    FAN -->|"Cout final"| CF["Carry Out<br/>(Flag C)"]

O carry final que sai do somador do bit 7 é exatamente o flag Carry (C) que você vê no registrador STATUS do PIC18F4550! Quando a soma de dois bytes ultrapassa 255 (ou seja, não cabe em 8 bits), esse carry final é 1, e o flag C é setado para avisar o programador que houve um transbordamento sem sinal.

A ULA Completa: Múltiplas Operações, Uma Saída

Uma ULA real não faz apenas adição — ela precisa realizar um menu de operações: adição, subtração, AND, OR, XOR, NOT, deslocamentos. O truque para implementar todas essas operações em um único circuito é simples mas elegante: calcule todas elas em paralelo e use um multiplexador para selecionar qual resultado aparece na saída.

flowchart TD
    subgraph ENTRADAS["Entradas"]
        A["Operando A<br/>(8 bits)"]
        B["Operando B<br/>(8 bits)"]
        SEL["Sinais de Controle<br/>da ULA (ALUControl)"]
    end
    
    subgraph CIRCUITOS["Circuitos Internos (operam em paralelo)"]
        ADD["Somador<br/>A + B"]
        SUB["Subtrator<br/>A - B"]
        AND_C["AND<br/>A AND B"]
        OR_C["OR<br/>A OR B"]
        XOR_C["XOR<br/>A XOR B"]
        NOT_C["NOT<br/>NOT A"]
        SHL["Shift Left<br/>A << 1"]
        SHR["Shift Right<br/>A >> 1"]
    end
    
    MUX["Multiplexador<br/>(selecionado por ALUControl)"]
    
    subgraph SAIDAS["Saídas"]
        RESULT["Resultado<br/>(8 bits)"]
        FLAGS["Flags de Status<br/>(Z, C, DC, OV, N)"]
    end
    
    A --> ADD
    A --> SUB
    A --> AND_C
    A --> OR_C
    A --> XOR_C
    A --> NOT_C
    A --> SHL
    A --> SHR
    B --> ADD
    B --> SUB
    B --> AND_C
    B --> OR_C
    B --> XOR_C
    SEL --> MUX
    ADD --> MUX
    SUB --> MUX
    AND_C --> MUX
    OR_C --> MUX
    XOR_C --> MUX
    NOT_C --> MUX
    SHL --> MUX
    SHR --> MUX
    MUX --> RESULT
    MUX --> FLAGS

Os sinais de controle da ULA (ALUControl) são uma palavra de bits que seleciona qual operação deve aparecer na saída do multiplexador. Esses sinais são gerados pela unidade de controle — que você estudará no próximo módulo — baseada no opcode da instrução que está sendo executada. Quando o opcode é ADDWF, os sinais de controle dizem ao multiplexador “selecione a saída do somador”. Quando é ANDWF, dizem “selecione a saída do circuito AND”.

A ULA do PIC18F4550

O PIC18F4550 possui uma ULA de 8 bits, o que significa que ela opera sobre palavras de um byte. Isso está diretamente alinhado com a natureza do microcontrolador: é um dispositivo de 8 bits projetado para aplicações embarcadas onde a eficiência energética e a simplicidade são mais importantes do que a capacidade de processar números grandes em uma única instrução.

Operações Suportadas pela ULA do PIC18F4550

As operações que a ULA do PIC18F4550 pode executar são dividas em quatro categorias:

Operações Aritméticas: adição (ADDWF, ADDLW), subtração (SUBWF, SUBLW), incremento (INCF) e decremento (DECF). A subtração é implementada usando complemento a dois: subtrair B de A é equivalente a adicionar A ao complemento de dois de B, ou seja, A + (\sim B + 1).

Operações Lógicas: AND bit a bit (ANDWF, ANDLW), OR bit a bit (IORWF, IORLW), XOR bit a bit (XORWF, XORLW) e complemento (COMF). Cada uma dessas operações afeta os flags Z e N do registrador STATUS.

Operações de Deslocamento: deslocamento à esquerda (RLCF, RLNCF) e à direita (RRCF, RRNCF), tanto com quanto sem o bit de carry. Os deslocamentos têm uso frequente em multiplicações e divisões por potências de 2, bem como em manipulação de bits individuais.

Operações de Transferência: MOVF, MOVWF, MOVLW — transferem dados sem modificá-los. Tecnicamente, mesmo uma transferência passa pela ULA (o dado é “somado com zero” ou simplesmente passado pelo circuito), o que permite atualizar os flags Z e N.

Os Flags de Status: A Memória de Curto Prazo da ULA

Toda vez que a ULA realiza uma operação, ela não produz apenas o resultado: ela também atualiza um conjunto de flags de status no registrador STATUS. Esses flags são como “post-its” que a ULA deixa para o processador, informando características do resultado. Sem os flags, você não poderia fazer desvios condicionais — e sem desvios condicionais, não haveria if, while ou for.

Registrador STATUS: Os Cinco Flags Essenciais

O registrador STATUS do PIC18F4550 mantém cinco flags atualizados pela ULA:

O flag Zero (Z) é setado (torna-se 1) quando o resultado de uma operação é zero. É o flag mais usado para implementar comparações: CPFSEQ compara dois valores subtraindo um do outro e verifica se Z é 1.

O flag Carry (C) é setado quando há carry fora do bit mais significativo em operações aritméticas, ou borrow em subtrações. Em deslocamentos, recebe o bit que “saiu pela borda”.

O flag Digit Carry (DC) é o carry do nibble inferior (bit 3) para o nibble superior (bit 4). É fundamental para aritmética BCD (Binary Coded Decimal), onde cada nibble representa um dígito decimal.

O flag Overflow (OV) é setado quando uma operação em complemento a dois produz resultado que não cabe no intervalo [-128, +127]. Diferente do Carry, que indica overflow sem sinal, OV indica overflow com sinal.

O flag Negative (N) espelha o bit mais significativo do resultado, indicando que o resultado é negativo em representação com sinal (complemento a dois).

flowchart LR
    ULA["ULA<br/>(8 bits)"] --> Z["Flag Z<br/>(Zero)"]
    ULA --> C["Flag C<br/>(Carry)"]
    ULA --> DC["Flag DC<br/>(Digit Carry)"]
    ULA --> OV["Flag OV<br/>(Overflow)"]
    ULA --> N["Flag N<br/>(Negative)"]
    Z --> STATUS["Registrador<br/>STATUS"]
    C --> STATUS
    DC --> STATUS
    OV --> STATUS
    N --> STATUS
    STATUS --> DEC["Unidade de Controle<br/>(desvios condicionais)"]

A tabela a seguir resume quais flags cada categoria de instrução afeta:

Instrução Z C DC OV N
ADD, ADDLW
SUB, SUBLW
AND, OR, XOR, COM
INC, DEC
MOV (com STATUS)
RLCF, RRCF
RLNCF, RRNCF

Observe a coluna do flag Carry para as instruções AND, OR e XOR: o traço (—) significa que essas instruções não afetam o flag C. Isso faz sentido conceitualmente — operações lógicas não produzem carry, pois operam bit a bit sem propagar transporte. Conhecer esses detalhes é fundamental para escrever código correto que usa flags para tomada de decisões.


O Banco de Registradores: Armazenamento de Alta Velocidade

Se a ULA é o coração computacional do processador, os registradores são o sangue que flui por ele. Um registrador é um elemento de armazenamento de alta velocidade construído diretamente no chip do processador, capaz de ser lido ou escrito em um único ciclo de clock. A diferença de velocidade entre um registrador e a memória RAM externa é de ordem de grandeza: enquanto um registrador responde em nanosegundos, uma leitura da RAM pode levar dezenas ou centenas de nanosegundos.

O motivo dessa diferença é físico: os registradores são construídos com células de SRAM estática diretamente adjacentes à ULA, interligados por trilhas metálicas curtíssimas dentro do chip. A memória RAM externa, ao contrário, precisa ser acessada através de barramentos que percorrem centímetros de trilha no circuito impresso — uma eternidade em termos de velocidade de sinal.

O Registrador de Trabalho W: O Acumulador Central

A arquitetura do PIC18F4550 é conhecida como arquitetura de acumulador, e o registrador W (Working Register, também chamado WREG) é esse acumulador. Na grande maioria das instruções aritméticas e lógicas, W participa como um dos operandos e frequentemente recebe o resultado. Isso simplifica o conjunto de instruções — você não precisa especificar dois registradores fonte, pois um deles é sempre W — mas exige planejamento cuidadoso do programador para evitar “perder” o conteúdo de W antes de utilizá-lo.

Exemplo: O Papel Central do Registrador W

Considere o cálculo simples resultado = a + b + c onde a, b e c são variáveis na RAM. Em assembly para o PIC18F4550, a sequência seria:

; Supondo a=0x10, b=0x11, c=0x12 (endereços na RAM)
MOVF  a, W, ACCESS    ; W ← a (carrega 'a' em W)
ADDWF b, W, ACCESS    ; W ← W + b (W agora contém a+b)
ADDWF c, W, ACCESS    ; W ← W + c (W agora contém a+b+c)
MOVWF resultado, ACCESS ; resultado ← W (salva na RAM)

Note que W é o ponto de passagem obrigatório: todo valor que entra na ULA passa por W. Se você precisasse calcular d = a + b e e = c + d imediatamente depois, precisaria salvar W antes de recomeçar, pois a segunda conta sobrescreveria o primeiro resultado.

Os Registradores de Propósito Geral (GPRs)

Além do registrador W, o PIC18F4550 possui 4096 bytes de RAM de dados organizados em 16 bancos de 256 bytes cada, dos quais a maior parte são registradores de propósito geral (General Purpose Registers, GPRs). Esses registradores armazenam variáveis locais, parâmetros de funções, resultados intermediários e qualquer dado que o programa precisa manter enquanto trabalha.

A organização em bancos surge de uma limitação de endereçamento: como o campo de endereço em muitas instruções tem apenas 8 bits, ele pode endereçar diretamente apenas 256 posições (0x00 a 0xFF). Para acessar qualquer posição além dessas 256, é necessário usar o BSR (Bank Select Register), que indica qual dos 16 bancos está atualmente ativo.

flowchart TB
    BSR["BSR<br/>(Bank Select Register)<br/>4 bits → 16 bancos"]
    
    subgraph BANCOS["Espaço de Endereçamento RAM (4096 bytes)"]
        B0["Banco 0<br/>0x000 - 0x0FF<br/>256 bytes (GPR)"]
        B1["Banco 1<br/>0x100 - 0x1FF<br/>256 bytes (GPR)"]
        B2["Banco 2<br/>0x200 - 0x2FF<br/>256 bytes (GPR)"]
        BDOTS["... (Bancos 3-14)"]
        B15["Banco 15<br/>0xF00 - 0xFFF<br/>SFRs e GPRs"]
    end
    
    ACCESS["Access Bank<br/>(acesso direto sem BSR):<br/>0x000-0x05F (GPR Banco 0)<br/>0xF60-0xFFF (SFRs Banco 15)"]
    
    BSR --> BANCOS
    BANCOS --> ACCESS

O Access Bank é um mecanismo muito conveniente: ele permite acessar os 96 bytes inferiores do Banco 0 (endereços 0x000–0x05F) e os 160 bytes superiores do Banco 15 (endereços 0xF60–0xFFF, onde ficam os SFRs mais importantes) sem precisar configurar o BSR. Muitas instruções têm um bit a que seleciona entre “usar BSR” e “usar Access Bank”. O compilador XC8 coloca automaticamente variáveis frequentemente usadas no Access Bank para evitar o overhead de chavear bancos.

Os Registradores de Função Especial (SFRs)

O Banco 15 contém os Registradores de Função Especial (Special Function Registers, SFRs), e estes são de importância central no seu Projeto Integrador. Cada SFR controla ou monitora um aspecto específico do hardware do PIC18F4550. Ao escrever um valor em um SFR, você está literalmente configurando transistores no chip — ligando ou desligando partes do hardware.

SFRs Mais Importantes para o Projeto Integrador

Os SFRs dividem-se em grupos funcionais que você encontrará repetidamente:

Controle de direção dos pinos I/O: Os registradores TRISA, TRISB, TRISC, TRISD e TRISE controlam se cada pino dos ports correspondentes é entrada (bit = 1) ou saída (bit = 0). O mnemônico TRIS vem de tri-state, o estado de alta impedância de uma entrada digital.

Leitura e escrita dos ports I/O: PORTA, PORTB, PORTC, PORTD e PORTE permitem ler o estado lógico dos pinos (quando configurados como entrada) ou escrever valores nos pinos (quando configurados como saída). LATA, LATB, LATC, LATD e LATE são os registradores de latch — eles representam o valor que o processador quer colocar nos pinos, independente do que está fisicamente no pino.

Controle do processador: STATUS guarda os flags da ULA. WREG é o registrador W. PCL, PCLATH e PCLATU compõem o Program Counter de 21 bits. STKPTR é o Stack Pointer (ponteiro da pilha de hardware do PIC18).

Controle de periféricos: ADCON0, ADCON1, ADCON2 controlam o conversor A/D. T0CON, T1CON, T2CON controlam os três timers. RCSTA, TXSTA controlam a UART serial. SSPCON1, SSPCON2 controlam o módulo SSP (SPI e I²C). Você explorará estes em profundidade nos Módulos 11–14.

flowchart LR
    CPU["Núcleo CPU<br/>(caminho de dados)"]
    
    CPU <-->|"escrita"| TRIS["TRISx<br/>(direção do pino)"]
    CPU <-->|"leitura/escrita"| PORT["PORTx / LATx<br/>(valor do pino)"]
    CPU <-->|"escrita"| ADCON["ADCONx<br/>(conversor A/D)"]
    CPU <-->|"escrita/leitura"| TCON["TxCON<br/>(timers)"]
    
    TRIS --> PIN["Pinos físicos<br/>do PIC18F4550"]
    PORT --> PIN
    ADCON --> ADC["Circuito<br/>A/D interno"]
    TCON --> TIMER["Circuitos<br/>de timer"]

Registradores de Controle do Fluxo: PC, SP e FSRs

Além dos GPRs e SFRs, existem registradores fundamentais para o controle do fluxo de execução:

O Program Counter (PC) é um registrador de 21 bits que sempre aponta para o endereço da próxima instrução a ser executada na memória de programa Flash. A cada instrução buscada, o PC é automaticamente incrementado em 2 (pois cada instrução ocupa 2 bytes de endereço, já que a memória de programa é endereçada em palavras de 16 bits). Quando uma instrução de desvio (GOTO, BRA, CALL) é executada, a ULA de cálculo de endereço carrega um novo valor no PC, redirecionando o fluxo do programa.

O Stack Pointer (SP) gerencia a pilha de hardware do PIC18F4550 — uma estrutura de dados LIFO (Last In, First Out) usada para salvar e restaurar o PC durante chamadas e retornos de sub-rotinas. O PIC18F4550 tem uma pilha de hardware de 31 níveis, o que significa que você pode ter no máximo 31 chamadas aninhadas sem retornar. Esta limitação é um aspecto arquitetural importante que influencia como você escreve código para este microcontrolador.

Os FSRs (File Select Registers) — FSR0, FSR1 e FSR2, cada um de 12 bits — são ponteiros de memória usados para endereçamento indireto. Quando você usa as instruções INDF0, INDF1 ou INDF2, o processador lê ou escreve na posição de memória apontada pelo FSR correspondente. Esse mecanismo é equivalente ao uso de ponteiros em C e é fundamental para manipular arrays e estruturas de dados em assembly.


Barramentos: As Autoestradas dos Dados

Imagine tentar conectar todos os componentes do processador diretamente uns aos outros: a ULA precisaria de fios indo para cada registrador, cada registrador precisaria de fios indo para a ULA e para os outros registradores, e assim por diante. Com apenas 16 registradores de 8 bits, isso já produziria centenas de conexões. A solução elegante adotada pelos projetistas de processadores é o barramento.

Um barramento (bus) é um conjunto de fios compartilhados por múltiplos componentes. Em vez de cada componente ter sua própria conexão privada com todos os outros, todos se conectam ao barramento. A cada momento, apenas um componente “fala” no barramento (coloca dados nele) enquanto um ou mais componentes “ouvem” (leem os dados). O controle de quem fala e quem ouve é responsabilidade da unidade de controle.

Tipos de Barramento no Caminho de Dados

No contexto do caminho de dados interno, os barramentos mais importantes são o barramento de dados, o barramento de endereços e o barramento de controle.

O barramento de dados interno transporta os operandos entre registradores e a ULA, bem como o resultado da ULA de volta para os registradores. No PIC18F4550, este barramento tem 8 bits de largura — o que significa que exatamente 8 bits são transferidos em paralelo a cada ciclo. Se você precisar processar um número de 16 bits (um int em C para PIC), duas transferências de 8 bits serão necessárias.

O barramento de endereços é usado para especificar qual posição de memória (RAM ou Flash) está sendo acessada. Para a memória de dados RAM, o PIC18F4550 usa um barramento de endereços de 12 bits (pois 2¹² = 4096, o tamanho da RAM). Para a memória de programa Flash, o barramento de endereços tem 21 bits (pois o PIC18F4550 tem até 2 MB de espaço endereçável de programa, embora o chip tenha apenas 32KB de Flash instalados).

flowchart TB
    subgraph CPU["Caminho de Dados Interno"]
        direction TB
        WREG["Registrador W"]
        ALU["ULA (8 bits)"]
        subgraph REG["Banco de Registradores"]
            GPR["GPRs"]
            SFR["SFRs"]
        end
        PC["Program Counter (21 bits)"]
        FSR["FSR0/1/2 (12 bits)"]
        
        WBUS["Barramento W (8 bits)"]
        DBUS["Barramento de Dados (8 bits)"]
        ABUS_D["Barramento de Endereços RAM (12 bits)"]
        ABUS_P["Barramento de Endereços Flash (21 bits)"]
    end
    
    DRAM["RAM de Dados<br/>(4 KB)"]
    PFLASH["Memória de Programa<br/>(Flash 32 KB)"]
    
    WREG <--> WBUS
    WBUS --> ALU
    REG <--> DBUS
    ALU --> DBUS
    DBUS --> WREG
    REG --> ABUS_D
    FSR --> ABUS_D
    ABUS_D --> DRAM
    DRAM <--> DBUS
    PC --> ABUS_P
    ABUS_P --> PFLASH
    PFLASH --> ALU

Multiplexadores: Os Controladores de Tráfego

Um barramento compartilhado introduz um problema: quando múltiplas fontes poderiam falar ao mesmo tempo, como o processador decide qual delas tem a vez? A resposta é o multiplexador (mux).

Um multiplexador é um circuito digital que recebe múltiplas entradas e uma entrada de seleção (select), e conecta exatamente uma das entradas à saída, com base no valor da seleção. É análogo a um interruptor de trilhos ferroviários: o trem pode vir de vários ramais, mas o interruptor determina por qual trilho ele seguirá.

Definição: Multiplexador (MUX)

Um multiplexador de 2^n entradas é um circuito combinacional com 2^n entradas de dados, n entradas de seleção (select) e uma saída. A saída reproduz o valor da entrada selecionada pelos bits de seleção.

Para um MUX 4:1 (4 entradas, 2 bits de seleção):

S1 S0 Saída
0 0 D0
0 1 D1
1 0 D2
1 1 D3

No caminho de dados, o sinal de seleção é gerado pela unidade de controle com base no opcode da instrução sendo executada. Isso é o que permite que a mesma ULA realize operações diferentes para instruções diferentes.

No caminho de dados do PIC18F4550, os multiplexadores aparecem em vários pontos críticos. O multiplexador de entrada da ULA seleciona se o segundo operando vem do registrador especificado pela instrução, de um literal embutido na instrução (endereçamento imediato), ou do FSR para operações de endereçamento indireto. O multiplexador de destino seleciona se o resultado vai para o registrador W ou para o registrador de arquivo especificado — exatamente o bit d das instruções como ADDWF f, d.


O Ciclo de Instrução: A Dança dos Componentes

Agora que você conhece cada componente do caminho de dados, está pronto para entender como eles trabalham juntos durante a execução de uma instrução. Este processo tem um nome clássico: o ciclo de instrução (instruction cycle).

O ciclo de instrução é dividido em fases, cada uma utilizando partes específicas do caminho de dados. As fases fundamentais são: busca (fetch), decodificação (decode), execução (execute) e escrita de resultado (write-back). Em alguns tipos de instrução, há também uma fase de acesso à memória (memory access) entre execução e write-back.

Fase 1: Busca (Fetch)

A fase de busca começa com o Program Counter apontando para o endereço da próxima instrução na memória Flash. O endereço contido no PC é colocado no barramento de endereços, a memória Flash é ativada e produz os 16 bits da instrução no barramento de dados. Esses 16 bits são carregados no Instruction Register (IR) — um registrador interno que mantém a instrução atual enquanto ela é processada.

Após a busca, o PC é incrementado automaticamente em 2 unidades (pois a Flash do PIC18 é endereçada em palavras de 16 bits e cada instrução ocupa uma palavra de 2 bytes no espaço de endereços), passando a apontar para a próxima instrução. Isso acontece em paralelo com as fases seguintes — este é o mecanismo do pipeline de dois estágios do PIC18F4550 que você estudou rapidamente no Módulo 1.

sequenceDiagram
    participant PC as Program Counter
    participant FLASH as Memória Flash
    participant IR as Instruction Register
    participant DEC as Decodificador
    
    PC->>FLASH: Endereço da instrução atual
    FLASH->>IR: Instrução de 16 bits
    PC->>PC: PC ← PC + 2 (automático)
    IR->>DEC: Envia instrução para decodificação
    Note over PC: PC já aponta para a próxima instrução

Fase 2: Decodificação (Decode)

Com a instrução no Instruction Register, a unidade de decodificação — que faz parte da unidade de controle — analisa os bits do opcode e determina:

  • Qual operação a ULA deve executar (gera os sinais ALUControl)
  • Quais registradores são os operandos fonte (gera os sinais de seleção dos multiplexadores de entrada)
  • Qual é o destino do resultado (gera os sinais de seleção do multiplexador de destino)
  • Se há acesso à memória e em que direção (leitura ou escrita)
  • Se o Program Counter precisa ser atualizado para um desvio

Em processadores simples, a decodificação é essencialmente uma grande tabela de verdade: o opcode entra como entrada e um conjunto de sinais de controle sai como saída. Para o PIC18F4550, com 75 instruções únicas, o decodificador precisa cobrir todos esses casos.

Fase 3: Execução (Execute)

Esta é a fase onde o “trabalho real” acontece. Com base nos sinais gerados na decodificação, os multiplexadores selecionam os operandos corretos, a ULA executa a operação especificada, e o resultado é disponibilizado na saída da ULA. Simultaneamente, os flags de status são atualizados.

Para instruções que não envolvem a ULA — como GOTO ou CALL — a “execução” consiste em calcular o novo valor do Program Counter. Para GOTO, o endereço de destino está codificado na instrução. Para CALL, o endereço de destino está na instrução e o endereço de retorno (PC+2) é salvo na pilha de hardware.

Fase 4: Acesso à Memória (Memory Access)

Esta fase é opcional e só ocorre para instruções que precisam ler ou escrever na RAM. Instruções como MOVF f, W precisam ler o conteúdo do endereço f na RAM. Instruções como MOVWF f precisam escrever o conteúdo de W no endereço f da RAM.

O acesso à memória RAM é mais lento que o acesso a registradores internos, mas ainda ocorre dentro de um único ciclo de máquina no PIC18F4550. Isso é possível porque a RAM interna do PIC18 é do tipo SRAM, construída no mesmo chip e acessível em um ciclo de clock.

Fase 5: Escrita de Resultado (Write-Back)

Na última fase, o resultado disponível na saída da ULA (ou lido da RAM) é escrito no registrador de destino. O multiplexador de destino seleciona, com base no bit d da instrução ou no tipo da instrução, se o resultado vai para o registrador W ou para o File Register especificado.

O diagrama completo do ciclo de instrução para a instrução ADDWF f, d ilustra como todas as fases se integram:

sequenceDiagram
    participant PC as Program Counter
    participant FLASH as Flash
    participant IR as Instruction Register
    participant DEC as Decodificador
    participant REG as Banco de Registradores
    participant MUX as Multiplexadores
    participant ALU as ULA
    participant STATUS as Registrador STATUS
    participant WREG as Registrador W

    Note over PC,WREG: Fase 1: Busca (Fetch)
    PC->>FLASH: Endereço (ex: 0x0010)
    FLASH->>IR: Instrução ADDWF (16 bits)
    PC->>PC: PC ← 0x0012

    Note over PC,WREG: Fase 2: Decodificação (Decode)
    IR->>DEC: Opcode = ADDWF
    DEC->>MUX: ALUControl = ADD
    DEC->>MUX: Src1 = WREG, Src2 = f
    DEC->>MUX: Dest = (d=0: WREG) ou (d=1: f)

    Note over PC,WREG: Fase 3: Execução (Execute)
    WREG->>MUX: Operando A
    REG->>MUX: Operando B (endereço f)
    MUX->>ALU: A e B selecionados
    ALU->>ALU: Calcula A + B

    Note over PC,WREG: Fase 4: Acesso à Memória
    REG->>MUX: Lê registrador f da RAM

    Note over PC,WREG: Fase 5: Write-Back
    ALU->>STATUS: Atualiza Z, C, DC, OV, N
    ALU->>MUX: Resultado disponível
    MUX->>WREG: Se d=0: salva em W
    MUX->>REG: Se d=1: salva em f

O Pipeline de Dois Estágios do PIC18F4550

O PIC18F4550 implementa um pipeline simples de dois estágios: enquanto uma instrução está na fase de execução (estágios 2-5), a instrução seguinte já está sendo buscada da Flash (fase 1). Isso significa que, em condições ideais (sem desvios), o processador completa uma instrução a cada ciclo de máquina (4 ciclos de clock), mesmo que cada instrução individualmente passe por múltiplas fases.

gantt
    title Pipeline de 2 Estágios do PIC18F4550
    dateFormat  X
    axisFormat %s

    section Instrução 1
    Busca (Fetch)    :done, 0, 1
    Execução         :done, 1, 2

    section Instrução 2
    Busca (Fetch)    :done, 1, 2
    Execução         :done, 2, 3

    section Instrução 3
    Busca (Fetch)    :done, 2, 3
    Execução         :done, 3, 4

    section Instrução 4 (desvio tomado)
    Busca (Fetch)    :done, 3, 4
    Execução         :done, 4, 5

    section Instrução 5 (descartada!)
    Busca (inválida) :crit, 4, 5
    Execução (nova)  :done, 5, 6

O problema aparece com desvios: quando uma instrução de desvio (GOTO, BRA, CALL) é executada, a instrução que já estava sendo buscada em paralelo pode ser do endereço errado. Quando o desvio é tomado, essa instrução é descartada e uma nova busca é iniciada no endereço correto de destino. Isso resulta em um ciclo de penalidade para desvios tomados — uma realidade que você perceberá ao medir tempos de execução no Projeto Integrador.


O Caminho de Dados Monociclo: Um Modelo Para Entender

Para consolidar todos os conceitos que você estudou neste módulo, é muito útil construir mentalmente (e no papel!) um caminho de dados monociclo — um modelo simplificado onde cada instrução completa em exatamente um ciclo de clock. Este modelo não é o que o PIC18F4550 usa internamente (o PIC18 tem pipeline e múltiplos ciclos de clock por instrução de máquina), mas é o ponto de partida pedagógico clássico para compreender como todos os componentes se encaixam.

A ideia central do caminho de dados monociclo é: todos os componentes necessários para executar qualquer instrução devem estar disponíveis simultaneamente, pois tudo acontece em um único ciclo de clock. Isso implica que alguns componentes precisam estar duplicados — por exemplo, memória de instrução e memória de dados devem ser separadas (não podemos ler uma instrução e ler um dado da mesma memória no mesmo ciclo).

Por que estudar o modelo monociclo se o PIC18 não usa esse modelo? Porque o caminho de dados monociclo revela a estrutura essencial que todo processador precisa ter: unidade de busca, decodificador, ULA, banco de registradores, interfaces de memória e multiplexadores de roteamento. Quando você entender o monociclo, entenderá por que o pipeline existe e quais problemas ele resolve — e isso será o tema central do Módulo 6.

Componentes do Caminho de Dados Monociclo

Vamos construir o caminho de dados monociclo para suportar três tipos representativos de instruções do PIC18F4550:

Tipo 1 — Instruções Registrador-Registrador (ex: ADDWF f, d): buscam dois operandos dos registradores, realizam operação na ULA, e escrevem o resultado de volta em um registrador.

Tipo 2 — Instruções de Desvio Incondicional (ex: GOTO): calculam um novo valor para o PC a partir de um campo de endereço na instrução.

Tipo 3 — Instruções de Carga com Imediato (ex: MOVLW k): carregam um valor literal (constante) da instrução diretamente no registrador W.

flowchart TB
    subgraph FETCH["Estágio de Busca"]
        PC2["Program Counter"]
        IMEM["Memória de<br/>Instruções (Flash)"]
        ADDER_PC["Somador<br/>PC + 2"]
        PC2 -->|"endereço"| IMEM
        PC2 --> ADDER_PC
        ADDER_PC -->|"PC+2"| MUX_PC["MUX<br/>PC+2 / Desvio"]
    end
    
    subgraph DECODE["Estágio de Decodificação"]
        IR2["Instruction Register"]
        CTRL["Unidade de<br/>Controle"]
        IMEM -->|"instrução 16 bits"| IR2
        IR2 -->|"opcode"| CTRL
    end
    
    subgraph EXECUTE["Estágio de Execução"]
        REGFILE["Banco de<br/>Registradores<br/>(W, GPRs)"]
        MUX_A["MUX<br/>Operando A"]
        MUX_B["MUX<br/>Operando B<br/>(reg ou imediato)"]
        ALU2["ULA<br/>8 bits"]
        SIGNEXT["Extensão de<br/>Sinal/Zero<br/>(para imediatos)"]
        
        IR2 -->|"endereço f"| REGFILE
        IR2 -->|"literal k"| SIGNEXT
        REGFILE -->|"W"| MUX_A
        REGFILE -->|"f"| MUX_B
        SIGNEXT -->|"k estendido"| MUX_B
        MUX_A --> ALU2
        MUX_B --> ALU2
    end
    
    subgraph WRITEBACK["Write-Back"]
        MUX_DEST["MUX<br/>Destino<br/>(W ou f)"]
        ALU2 -->|"resultado"| MUX_DEST
        MUX_DEST -->|"escreve W"| REGFILE
        MUX_DEST -->|"escreve f"| REGFILE
    end
    
    CTRL -->|"ALUControl"| ALU2
    CTRL -->|"SelA, SelB"| MUX_A
    CTRL -->|"SelA, SelB"| MUX_B
    CTRL -->|"DestSel"| MUX_DEST
    CTRL -->|"PCSel"| MUX_PC
    MUX_PC -->|"novo PC"| PC2
    
    ALU2 -->|"endereço desvio"| MUX_PC

Como Cada Tipo de Instrução Atravessa o Caminho

Para uma instrução Tipo 1 como ADDWF f, W (d=0, resultado em W):

A unidade de controle gera: ALUControl = ADD, SelB = registrador (não imediato), DestSel = W. O banco de registradores fornece W para a entrada A da ULA e o conteúdo do endereço f para a entrada B. A ULA realiza a adição, o resultado é roteado pelo MUX de destino para escrever de volta em W, e os flags são atualizados no STATUS.

Para uma instrução Tipo 3 como MOVLW k (carrega literal em W):

A unidade de controle gera: ALUControl = PASS (passa o operando B direto para a saída, sem modificação), SelB = imediato (usa o literal k da instrução). O valor k de 8 bits extraído da instrução é conectado à entrada B da ULA. A ULA “executa” uma operação de passagem e o resultado (que é simplesmente k) é escrito em W. Note que esta operação não afeta os flags de status — verifique na tabela de flags que vimos anteriormente.

Para uma instrução Tipo 2 como GOTO n (desvio incondicional):

A unidade de controle gera: PCSel = desvio. O campo de 20 bits da instrução que contém o endereço de destino n é conectado ao MUX do PC. Em vez de PC+2, o novo valor do PC é o endereço n, e na próxima busca o processador começará a executar a partir desse novo endereço.


Colocando em Prática: Análise do Caminho de Dados no PIC18F4550

Agora que você tem o modelo conceitual claro, vamos conectá-lo com o hardware real que você usa no Projeto Integrador.

Analisando o Registrador STATUS em Código Real

Considere o seguinte trecho de código C típico de um sistema embarcado:

#include <xc.h>
#include <stdint.h>

/* Verifica se a soma de dois bytes resulta em overflow sem sinal */
void verifica_overflow(uint8_t a, uint8_t b) {
    uint8_t soma = a + b;
    
    if (soma < a) {
        /* Houve overflow sem sinal! O Carry foi setado */
        LATD = 0xFF;  /* Acende todos os LEDs */
    } else {
        LATD = soma;  /* Mostra o resultado nos LEDs */
    }
}
; Parâmetros em W (a) e registrador aux (b)
; Compilado com XC8 -O1

_verifica_overflow:
    MOVWF   _a, ACCESS      ; salva 'a' na RAM
    MOVWF   _soma_temp      ; copia 'a' para calcular
    MOVF    _b, W, ACCESS   ; W ← b
    ADDWF   _a, W, ACCESS   ; W ← a + b (CARRY atualizado!)
    MOVWF   _soma, ACCESS   ; salva soma
    
    ; Verifica if (soma < a): equivale a CARRY setado após adição?
    ; O compilador compara soma e a usando subtração
    SUBWF   _a, W, ACCESS   ; W ← a - soma; se soma > a, CARRY = 0
    BNC     _else_branch     ; desvia se Carry = 0 (sem overflow)
    
    ; Ramo then: houve overflow
    MOVLW   0xFF
    MOVWF   LATD, ACCESS    ; LATD ← 0xFF
    RETURN
    
_else_branch:
    MOVF    _soma, W, ACCESS
    MOVWF   LATD, ACCESS    ; LATD ← soma
    RETURN

A coisa mais interessante neste exemplo é observar que a detecção de overflow (if (soma < a)) é implementada pelo compilador usando uma subtração seguida de verificação do flag Carry. Isso revela algo importante: os flags de status da ULA são a ponte entre a aritmética e o fluxo de controle do programa. Toda instrução condicional em C se traduz, em última instância, em uma operação que atualiza flags e uma instrução de desvio condicional que verifica esses flags.

Medindo o Custo Real das Operações

Um aspecto prático fundamental que você explorará no Projeto Integrador é medir quantos ciclos de máquina cada tipo de operação consome. A tabela a seguir resume o custo das instruções mais comuns do PIC18F4550:

Categoria de Instrução Exemplos Ciclos de Máquina
Operações registrador ↔︎ ULA ADDWF, ANDWF, MOVF 1
Operações com literal ADDLW, ANDLW, MOVLW 1
Leitura de registrador para W MOVF f, W 1
Escrita de W para registrador MOVWF f 1
Desvio não tomado BRA, BC, BZ (condição falsa) 1
Desvio tomado (relativo) BRA (condição verdadeira) 2
Desvio incondicional longo GOTO 2
Chamada de sub-rotina CALL 2
Retorno de sub-rotina RETURN, RETLW 2
Pulo condicional (skip) CPFSEQ, DECFSZ (condição falsa) 1
Pulo condicional (skip) CPFSEQ, DECFSZ (condição verdad) 2
Operação sobre tabela TBLRD, TBLWT 2

O fato de desvios tomados custarem 2 ciclos enquanto desvios não tomados custam apenas 1 tem implicações diretas para otimização de laços. Se você tem um laço for que itera 100 vezes, o desvio de retorno ao início do laço é tomado 99 vezes (custo = 99 × 2 = 198 ciclos) e não tomado 1 vez na saída (custo = 1 ciclo). Esse overhead do pipeline é parte do custo real de qualquer laço em assembly ou C.

Exemplo Prático: Calculando o Tempo de Execução de um Laço

Suponha que você tenha o seguinte laço em assembly, que limpa 32 bytes de RAM:

; FSR0 aponta para o início do buffer
; WREG = 0x00 (preparado antes do laço)
; Contador em registrador aux

    MOVLW   0x20            ; 1 ciclo — carrega 32 no contador
    MOVWF   contador, ACCESS ; 1 ciclo — salva contador

loop:
    CLRF    INDF0, ACCESS   ; 1 ciclo — limpa byte apontado por FSR0
    MOVLW   1               ; 1 ciclo — prepara incremento
    ADDWF   FSR0L, F, ACCESS ; 1 ciclo — incrementa FSR0
    DECFSZ  contador, F, ACCESS ; 1 ciclo (não pulou) ou 2 ciclos (pulou)
    BRA     loop            ; 2 ciclos (quando tomado) ou 1 (quando não)
    
; Após o laço: próxima instrução

Calculando o tempo total:

Para as 32 iterações, o corpo do laço (excluindo o DECFSZ e BRA finais) consome 32 \times 3 = 96 ciclos para as instruções CLRF, MOVLW e ADDWF.

O DECFSZ custa 1 ciclo nas primeiras 31 iterações (não pula) e 2 ciclos na última (pula): 31 \times 1 + 1 \times 2 = 33 ciclos.

O BRA custa 2 ciclos nas primeiras 31 iterações (desvio tomado) e não é executado na última: 31 \times 2 = 62 ciclos.

Mais os 2 ciclos de inicialização antes do laço: 2 ciclos.

Total: 2 + 96 + 33 + 62 = 193 ciclos de máquina.

A 48 MHz com clock de instrução a 12 MHz (cada ciclo de máquina = 4 ciclos de clock), cada ciclo de máquina dura \frac{1}{12 \times 10^6} \approx 83,3 ns.

Tempo total: 193 \times 83,3 \text{ ns} \approx 16,1 \text{ μs}

Otimização Usando o Caminho de Dados

Compreender o caminho de dados revela oportunidades de otimização que não são óbvias olhando apenas para o código C. A regra de ouro é: dados em registradores são sempre mais rápidos que dados na RAM, porque acessar a RAM exige um ciclo de barramento de endereços e um ciclo de dados, enquanto um registrador já está fisicamente conectado à ULA.

No PIC18F4550, o “melhor registrador” é o próprio W: instruções como ADDLW e ANDLW operam diretamente sobre W com um literal, sem precisar nem mesmo acessar a RAM. A segunda melhor opção é o Access Bank — os primeiros 96 bytes do Banco 0 e os SFRs do Banco 15 são acessíveis sem precisar configurar o BSR.

Exemplo: Impacto da Alocação de Variáveis no Desempenho

Considere duas versões de um cálculo simples:

Versão 1 — Variáveis em banco diferente do Access Bank:

; variáveis no Banco 3 (precisa configurar BSR)
MOVLB   3               ; 1 ciclo — seleciona Banco 3
MOVF    var_a, W, BANKED ; 1 ciclo — carrega var_a
ADDWF   var_b, W, BANKED ; 1 ciclo — soma var_b
MOVWF   var_c, BANKED   ; 1 ciclo — salva resultado
; Total: 4 ciclos

Versão 2 — Variáveis no Access Bank:

; variáveis no Banco 0, endereços 0x00-0x5F
MOVF    var_a, W, ACCESS ; 1 ciclo — carrega var_a (sem precisar de MOVLB)
ADDWF   var_b, W, ACCESS ; 1 ciclo — soma var_b
MOVWF   var_c, ACCESS   ; 1 ciclo — salva resultado
; Total: 3 ciclos (25% mais rápido!)

A versão 2 é 25% mais rápida apenas por posicionar as variáveis no Access Bank. O compilador XC8 faz isso automaticamente para variáveis static e globais declaradas antes de qualquer outra alocação. Para forçar uma variável no Access Bank em código C com XC8, use o atributo __near:

static uint8_t __near var_a, var_b, var_c;

Registradores Especiais do Caminho de Dados: Uma Visão Integrada

Para consolidar sua compreensão, vamos traçar como todos os registradores especiais do PIC18F4550 se encaixam no caminho de dados que você estudou:

flowchart TB
    subgraph FLUXO_PROGRAMA["Controle de Fluxo"]
        PC_REG["PC (21 bits)<br/>Program Counter"]
        SP_REG["STKPTR<br/>Stack Pointer (5 bits)"]
        STACK_MEM["Pilha de Hardware<br/>31 níveis × 21 bits"]
        PC_REG -->|"salvo em CALL"| STACK_MEM
        STACK_MEM -->|"restaurado em RETURN"| PC_REG
        SP_REG -->|"controla"| STACK_MEM
    end
    
    subgraph DADOS["Caminho de Dados"]
        WREG2["WREG<br/>(acumulador)"]
        BSR2["BSR<br/>(4 bits)"]
        STATUS2["STATUS<br/>Z C DC OV N"]
        FSR0_R["FSR0 (12 bits)<br/>ponteiro indireto"]
        FSR1_R["FSR1 (12 bits)<br/>ponteiro indireto"]
        FSR2_R["FSR2 (12 bits)<br/>ponteiro indireto"]
        ALU3["ULA"]
        
        WREG2 <-->|"operandos"| ALU3
        ALU3 -->|"atualiza"| STATUS2
        STATUS2 -->|"flags"| PC_REG
        BSR2 -->|"seleciona banco"| RAM_BLOCK["RAM 4KB"]
        FSR0_R -->|"endereço indireto"| RAM_BLOCK
        FSR1_R -->|"endereço indireto"| RAM_BLOCK
        FSR2_R -->|"endereço indireto"| RAM_BLOCK
        RAM_BLOCK <-->|"dados"| ALU3
    end
    
    subgraph PERIFERICO2["Interface com Periféricos"]
        TRIS_R["TRISx<br/>(direção I/O)"]
        PORT_R["PORTx / LATx<br/>(valor I/O)"]
        TRIS_R -->|"configura"| PINOS["Pinos físicos"]
        PORT_R <-->|"lê/escreve"| PINOS
        RAM_BLOCK <-->|"SFRs"| TRIS_R
        RAM_BLOCK <-->|"SFRs"| PORT_R
    end

Esse diagrama integrado mostra que o caminho de dados não é apenas a ULA e os registradores — ele inclui toda a infraestrutura que permite ao processador buscar instruções, gerenciar sub-rotinas através da pilha, acessar memória de forma direta e indireta, e interagir com os periféricos do chip.


Conexão com o Projeto Integrador

Neste módulo, o trabalho prático do Projeto Integrador tem dois focos principais que aplicam diretamente o que você acabou de estudar.

O primeiro foco é a otimização de registradores: você e seu grupo irão identificar uma seção do código do projeto que realiza operações repetidas (provavelmente algum cálculo de display ou processamento de sensor), analisar o assembly gerado pelo compilador MPLAB X, e refatorar o código C para reduzir o número de acessos à RAM, mantendo dados frequentemente usados no registrador W ou no Access Bank. A seção de Disassembly Listing do MPLAB X (menu Window → Debugging → Disassembly) mostrará exatamente quais instruções estão sendo geradas.

O segundo foco é a medição do ciclo de instrução: usando o simulador do MPLAB X e os timers do PIC18F4550, você criará uma tabela de referência com o número de ciclos que diferentes classes de instruções consomem. Para isso, você usará o Timer0 como cronômetro de alta resolução — configurado para contar ciclos de instrução — e medirá o tempo de execução de blocos de código cuidadosamente construídos.

Atenção para a sessão de tutoria: Antes de medir tempos, certifique-se de que o Timer0 está configurado com prescaler 1:1 e que você sabe como ler seus 16 bits (THigh e TLow). Consulte a seção de Timer0 no datasheet do PIC18F4550 (páginas 125-138) para entender os registradores T0CON, TMR0H e TMR0L.


Síntese: O Que Você Aprendeu Neste Módulo

Você atravessou um módulo denso e repleto de conceitos fundamentais. Vamos recapitular o que foi construído.

O caminho de dados é a infraestrutura física do processador que move e transforma informação. Ele é composto pela ULA, pelo banco de registradores, pelos barramentos e pelos multiplexadores — e esses componentes trabalham em coordenação a cada ciclo de clock para executar as instruções que você escreve.

A ULA é construída a partir de portas lógicas organizadas em circuitos combinacionais. Para operações lógicas, são circuitos simples e paralelos. Para adição, o somador ripple-carry propaga o carry da posição menos significativa para a mais significativa. Todos os circuitos operam em paralelo e um multiplexador seleciona qual resultado aparece na saída. Os flags de status (Z, C, DC, OV, N) no registrador STATUS são a ponte entre a ULA e as instruções de desvio condicional.

O banco de registradores do PIC18F4550 é organizado em 16 bancos de 256 bytes, com o BSR selecionando o banco ativo. O registrador W é o acumulador central — obrigatório em quase todas as instruções aritméticas e lógicas. O Access Bank oferece acesso rápido aos GPRs do Banco 0 e aos SFRs do Banco 15 sem precisar configurar o BSR. Os SFRs são a interface entre o núcleo do processador e os periféricos do chip.

O ciclo de instrução divide-se em cinco fases: busca, decodificação, execução, acesso à memória e escrita de resultado. O pipeline de dois estágios do PIC18F4550 sobrepõe a busca da próxima instrução com a execução da atual, alcançando throughput de uma instrução por ciclo de máquina — exceto em desvios tomados, que custam um ciclo extra de penalidade.

O caminho de dados monociclo é o modelo conceitual que revela a estrutura essencial de qualquer processador: busca, decodificação, ULA, registradores, memória e multiplexadores. Entendê-lo é o fundamento para compreender o pipeline (Módulo 6) e a unidade de controle (Módulo 5), que você estudará nas próximas semanas.

Antes da próxima aula (Módulo 5): Leia o manual do MPLAB X sobre a janela de Registers e o painel de Watch (variáveis monitoradas). Explore o registrador STATUS durante a execução passo a passo de um programa simples com operações aritméticas. Anote quando os flags Z, C e N mudam e por quê. Essa observação direta dos flags em ação será o ponto de partida perfeito para entender como a unidade de controle usa esses flags para tomar decisões de desvio.


A Subtração e o Complemento a Dois: Uma Elegância Arquitetural

Você pode ter estranhado que a tabela de operações da ULA lista “subtrator” como um circuito separado, mas na prática a grande maioria das ULAs reais — incluindo a do PIC18F4550 — não implementa subtração com um circuito independente. Em vez disso, utiliza uma propriedade matemática que torna a subtração equivalente a uma adição: o complemento a dois.

Lembre-se do Módulo 2: o complemento a dois de um número B de n bits é definido como \overline{B} + 1, onde \overline{B} é o complemento a um (inversão bit a bit). A propriedade fundamental é:

A - B = A + (-B) = A + \overline{B} + 1

Isso significa que para subtrair B de A, a ULA pode simplesmente: (1) inverter todos os bits de B (operação NOT, que ela já sabe fazer), (2) somar 1 ao resultado (que equivale a colocar o carry de entrada do somador como 1), e (3) somar com A usando o somador ripple-carry que ela já tem. Nenhum circuito adicional de subtração é necessário!

flowchart LR
    subgraph ADD_BLOCK["Somador Ripple-Carry"]
        S["Soma A + B'"]
    end
    
    A2["Operando A"] --> S
    B2["Operando B"] --> INV["Inversores<br/>(NOT bit a bit)"]
    INV -->|"B' = NOT(B)"| S
    CTRL_SUB["Sinal de Controle<br/>(ADD=0 / SUB=1)"] -->|"Cin = 1"| S
    S --> RESULT2["A - B"]
    
    Note["Quando SUB=1:<br/>Cin recebe 1 (em vez de 0)<br/>B é invertido antes de somar<br/>Resultado = A + NOT(B) + 1 = A - B"]
    CTRL_SUB --> Note

Essa unificação é mais do que elegância matemática — ela tem impacto direto no hardware: a ULA física precisa de apenas um somador, não dois circuitos separados. O custo extra é um conjunto de multiplexadores que decide se B deve ser invertido ou não (controlado pelo sinal de subtração), e se o carry de entrada deve ser 0 (adição) ou 1 (subtração). Isso reduz o número de transistores e, portanto, o consumo de energia e o custo do chip.

O flag Carry no registrador STATUS ganha um significado específico nesse contexto. Em adição, Carry indica que o resultado ultrapassou 255 (overflow sem sinal). Em subtração, o Carry é na verdade o borrow inverso: Carry = 1 indica que a subtração aconteceu sem empréstimo (ou seja, A \geq B), enquanto Carry = 0 indica que houve empréstimo (A < B). Essa inversão pode parecer confusa, mas é consistente com a implementação via complemento a dois — e é por isso que a instrução BNC (Branch if No Carry) é usada para detectar “menor que” em comparações sem sinal.


Deslocamentos e Rotações: Multiplicação e Divisão Gratuitas

Uma categoria de operações da ULA merece atenção especial por sua utilidade prática no Projeto Integrador: os deslocamentos (shifts) e rotações (rotates). Essas operações movem todos os bits de um registrador uma posição para a esquerda ou para a direita, e têm um efeito matemático muito conveniente.

Deslocar um número binário uma posição para a esquerda equivale a multiplicá-lo por 2. Deslocar uma posição para a direita equivale a dividi-lo por 2 (divisão inteira). Isso ocorre porque na representação binária, cada posição à esquerda representa o dobro do valor da posição anterior — exatamente como no sistema decimal, onde mover um dígito para a esquerda equivale a multiplicar por 10.

O PIC18F4550 oferece quatro variantes dessas operações, cujas diferenças estão em como o bit que “sai pela borda” é tratado e de onde vem o bit que “entra pela outra borda”:

A instrução RLCF (Rotate Left through Carry) desloca todos os bits um posição para a esquerda. O bit que estava na posição 7 (mais significativa) vai para o flag Carry. O bit que entra na posição 0 vem do flag Carry anterior. Isso cria uma rotação de 9 bits que passa pelo registrador de status — extremamente útil para deslocar números de 16 bits armazenados em dois registradores de 8 bits.

A instrução RLNCF (Rotate Left, No Carry) faz uma rotação de 8 bits pura: o bit que sai pela esquerda entra pela direita, sem envolver o flag Carry. O flag Carry é atualizado com o bit que saiu, mas não é usado como entrada.

Exemplo: Multiplicando por 4 Usando Deslocamentos

O cálculo resultado = valor * 4 pode ser implementado com dois deslocamentos à esquerda, sem usar multiplicação:

/* Em C: */
uint8_t resultado = valor << 2;   /* equivale a valor * 4 */

Em assembly, o compilador provavelmente gerará:

; W contém 'valor'
RLNCF   valor, W, ACCESS   ; W ← valor << 1 (× 2); Carry = bit 7 anterior
RLNCF   WREG, W, ACCESS    ; W ← W << 1 (× 4); Carry = bit 7 anterior
MOVWF   resultado, ACCESS  ; salva resultado

Dois deslocamentos à esquerda = multiplicação por 4, tudo em 3 ciclos de máquina.

Compare com o custo de uma multiplicação real de 8 bits no PIC18F4550: como o PIC18F4550 não tem instrução de multiplicação de 8×8 bits com resultado de 8 bits no caminho de dados principal (ele tem MULWF para multiplicação de 8×8 com resultado de 16 bits nos registradores PRODH:PRODL), deslocamentos são frequentemente a abordagem mais eficiente para potências de 2.

Atenção: para valores com sinal (inteiros com sinal em complemento a dois), deslocamentos à direita devem preservar o bit de sinal para manter o sinal do número. Isso é chamado de deslocamento aritmético — o bit que entra pela esquerda é uma cópia do bit de sinal, não zero. Para tipos unsigned, o deslocamento lógico (zero entra pela esquerda) é correto. O compilador XC8 usa automaticamente a versão correta baseada no tipo da variável.

A instrução MULWF merece menção especial: ela realiza multiplicação de 8×8 bits com resultado de 16 bits, armazenados no par de registradores PRODH:PRODL. Essa é uma capacidade relativamente avançada para um microcontrolador de 8 bits — muitos MCUs de 8 bits de gerações anteriores não tinham hardware de multiplicação e precisavam implementá-la via software com deslocamentos e somas. No PIC18F4550, MULWF executa em apenas 1 ciclo de máquina, tornando-o adequado até para aplicações que exigem aritmética de ponto fixo.


O Banco de Registradores em Perspectiva: Comparando com Outras Arquiteturas

Uma pergunta natural ao estudar o PIC18F4550 é: “por que tão poucos registradores de propósito geral explícitos?” — afinal, a instrução ADDWF tem apenas um registrador de propósito geral nomeado (o File Register f) além do acumulador W. Compare isso com um processador ARM Cortex-M, que tem 16 registradores de 32 bits todos intercambiáveis.

A resposta está na filosofia de projeto. O PIC18F4550 é um descendente de uma família de microcontroladores projetada nos anos 1980 para aplicações embarcadas simples, onde o custo do chip era a restrição principal. Um banco de registradores com 16 registradores de 32 bits como o ARM requer mais transistores, mais área de chip, mais energia — tudo isso em conflito com os requisitos de um microcontrolador barato para controlar um eletrodoméstico ou um automóvel.

A arquitetura de acumulador com banco de registradores na RAM foi uma solução engenhosa para esse compromisso: ao tratar toda a RAM interna como “banco de registradores endereçável”, o PIC oferece uma quantidade enorme de armazenamento local (centenas de bytes) ao custo de ter apenas um registro de trabalho rápido (W). O programador paga o preço de ter que gerenciar explicitamente o conteúdo de W, mas ganha um chip muito menor e mais barato.

Comparativo: Filosofias de Banco de Registradores

Característica PIC18F4550 ARM Cortex-M0+ x86-64
Registradores GPR 1 (W) + RAM 16 × 32 bits 16 × 64 bits
Largura de dados 8 bits 32 bits 64 bits
Filosofia Acumulador Load-Store RISC CISC acumulador
Acesso à RAM 1 ciclo (on-chip) 1+ ciclos (AHB) Vários ciclos
Tamanho do chip Muito pequeno Pequeno Grande
Aplicações típicas MCU embarcado MCU IoT Computadores

Note que o ARM Cortex-M0+ (presente no Arduino Zero, por exemplo) tem 16 registradores intercambiáveis — uma abordagem mais flexível que a do PIC, mas ainda conservadora comparada ao x86-64. O princípio é que registradores são a memória mais rápida disponível: quanto mais deles você tiver, menos vezes o processador precisará ir buscar dados na memória mais lenta.

Para o seu trabalho no Projeto Integrador, essa comparação tem uma implicação prática direta: ao escrever código C para o PIC18F4550, você deve ser muito consciente de quando está “derramando” variáveis da RAM para dentro e para fora da ULA. Funções com muitas variáveis locais podem gerar código assembly ineficiente porque o compilador precisa fazer muitos MOVF e MOVWF para carregar e salvar variáveis de ida e volta da RAM para W. A boa prática de manter funções curtas e com poucas variáveis locais não é apenas uma questão de estilo — é uma estratégia de desempenho real no PIC18F4550.


Endereçamento Indireto: FSRs Como Ponteiros em Hardware

No Módulo 3, você aprendeu sobre o modo de endereçamento indireto — quando o endereço efetivo do operando não está na instrução, mas sim em um registrador que funciona como ponteiro. No PIC18F4550, os FSRs (File Select Registers) implementam exatamente esse mecanismo, e eles são componentes do caminho de dados que merecem atenção especial porque você os usará frequentemente no Projeto Integrador ao trabalhar com arrays e buffers.

O PIC18F4550 tem três pares FSR: FSR0 (composto por FSR0H:FSR0L), FSR1 (FSR1H:FSR1L) e FSR2 (FSR2H:FSR2L). Cada FSR é um registrador de 12 bits que aponta para qualquer posição do espaço de dados RAM. Para ler o dado apontado por FSR0, você usa a instrução MOVF INDF0, W — onde INDF0 é um endereço especial que não corresponde a nenhuma localização real, mas instrui o processador a usar FSR0 como endereço. O caminho de dados detalha esse processo:

sequenceDiagram
    participant IR3 as Instruction Register
    participant DEC2 as Decodificador
    participant FSR_R as FSR0 (12 bits)
    participant MUXADDR as MUX Endereço RAM
    participant RAM2 as RAM de Dados
    participant ALU4 as ULA
    participant WREG3 as Registrador W

    IR3->>DEC2: MOVF INDF0, W (instrução)
    DEC2->>MUXADDR: Seleciona "modo indireto via FSR0"
    FSR0_R->>MUXADDR: Fornece endereço (12 bits)
    MUXADDR->>RAM2: Apresenta endereço do FSR0
    RAM2->>ALU4: Dado lido da RAM
    ALU4->>WREG3: W ← dado (operação de passagem)

O PIC18F4550 tem variantes das instruções de endereçamento indireto que fazem operações de pós-incremento e pré-decremento automaticamente. MOVF POSTINC0, W lê o valor apontado por FSR0 e depois incrementa FSR0. MOVF PREINC0, W primeiro incrementa FSR0 e depois lê. MOVF POSTDEC0, W lê e depois decrementa. Essas variantes são o equivalente exato dos operadores *p++ e *--p em C, e permitem percorrer arrays de forma muito eficiente.

Exemplo: Somando um Array com Endereçamento Indireto

Suponha que você tem um array de 8 bytes na RAM e quer calcular a soma de todos os seus elementos:

/* Em C: */
uint8_t dados[8] = {10, 20, 30, 40, 50, 60, 70, 80};
uint8_t soma_total = 0;

for (int i = 0; i < 8; i++) {
    soma_total += dados[i];
}

O assembly equivalente usando FSR0:

; Inicializa FSR0 para apontar para o início do array
MOVLW   LOW(dados)         ; byte baixo do endereço
MOVWF   FSR0L, ACCESS
MOVLW   HIGH(dados)        ; byte alto do endereço
MOVWF   FSR0H, ACCESS

; Inicializa contador e acumulador
MOVLW   8                  ; 8 iterações
MOVWF   contador, ACCESS
CLRF    soma_total, ACCESS ; soma_total = 0

loop_soma:
    MOVF    POSTINC0, W, ACCESS  ; W ← *FSR0; FSR0++
    ADDWF   soma_total, F, ACCESS ; soma_total += W
    DECFSZ  contador, F, ACCESS   ; contador--; pula se zero
    BRA     loop_soma

; soma_total agora contém a soma dos 8 elementos

O uso de POSTINC0 elimina a instrução de incremento do ponteiro que seria necessária em endereçamento direto, tornando o laço mais compacto e rápido.


Entendendo o Timing: Clock, Ciclos e a Relação com o Caminho de Dados

Uma questão que frequentemente gera confusão nos estudantes é a relação entre a frequência do oscilador (clock externo ou interno), o clock de instrução e os ciclos de máquina. Compreender essa relação é essencial para calcular tempos de execução no Projeto Integrador.

O PIC18F4550 tem um oscilador que pode ser configurado para diferentes frequências. Quando conectado ao cristal de 20 MHz do kit ACEPIC PRO V8.2 e configurado com o PLL interno, o oscilador efetivo opera a 48 MHz. Cada oscilação do cristal é um ciclo de clock — e é a unidade mais básica de tempo no processador.

O clock de instrução, internamente chamado de Fosc/4 (frequência do oscilador dividida por 4), é a frequência com que a maioria das instruções é executada. No caso do kit com PLL a 48 MHz, o clock de instrução é 48 / 4 = 12 MHz. Isso significa que, a cada 4 ciclos de oscilador, ocorre um ciclo de instrução — cada ciclo de instrução dura 1/12 \times 10^6 \approx 83,3 ns.

Por que dividir por 4? Porque o PIC18F4550 usa um clock de quatro fases interno (Q1, Q2, Q3, Q4) para coordenar as diferentes atividades dentro do caminho de dados durante um ciclo de instrução. Na fase Q1, a instrução é decodificada. Na fase Q2, os registradores são lidos. Na fase Q3, a ULA executa a operação. Na fase Q4, o resultado é escrito de volta e a próxima busca começa. Essas quatro fases juntas constituem um ciclo de máquina completo.

gantt
    title Ciclo de Máquina do PIC18F4550 (48 MHz → 12 MHz instrução)
    dateFormat X
    axisFormat Q%s

    section Ciclos de Clock (Fosc = 48 MHz)
    Q1 :done, 0, 1
    Q2 :done, 1, 2
    Q3 :done, 2, 3
    Q4 :done, 3, 4

    section Atividades do Caminho de Dados
    Decodificação (Q1)      :active, 0, 1
    Leitura Registradores (Q2) :active, 1, 2
    Execução ULA (Q3)       :active, 2, 3
    Write-Back + Fetch (Q4) :active, 3, 4

Essa organização em quatro fases é relevante para você porque determina como os periféricos do PIC18F4550 funcionam. Por exemplo, os timers e o módulo de captura/comparação/PWM (CCP) têm resolução de 1 ciclo de instrução (83,3 ns a 48 MHz), não de 1 ciclo de clock (20,8 ns). Portanto, ao configurar um timer para gerar uma frequência específica, o período mínimo configurável é de 83,3 ns, não de 20,8 ns.


Uma Visão Histórica: Como o Caminho de Dados Evoluiu

Para colocar o caminho de dados do PIC18F4550 em perspectiva, é instrutivo traçar brevemente como os caminhos de dados evoluíram ao longo da história dos processadores. Isso não é apenas história — é um relato de como as pressões de engenharia moldaram as decisões de projeto que você estuda hoje.

Os primeiros microprocessadores dos anos 1970, como o Intel 4004 e o 8080, tinham caminhos de dados extremamente simples: uma ULA de 4 ou 8 bits, um punhado de registradores (tipicamente A, B, C, D, E, H, L no 8080), e um barramento único pelo qual tanto dados quanto endereços eram multiplexados no tempo. A simplicidade era necessária porque a tecnologia de fabricação da época limitava o número de transistores por chip.

O MOS 6502 (usado no Apple II e no Atari), contemporâneo do 8080, fez escolhas ligeiramente diferentes: acumulador central (como o PIC), X e Y como registradores de índice, e endereçamento indireto via “página zero” (os primeiros 256 bytes da RAM). Essas escolhas influenciaram diretamente o projeto da família PIC — a organização em bancos de memória e o Access Bank do PIC18 são descendentes conceituais da página zero do 6502.

Os anos 1980 trouxeram os processadores de 16 e 32 bits (8086, 68000, SPARC, MIPS) e com eles o debate CISC versus RISC que você estudou no Módulo 3. Os processadores RISC simplificaram drasticamente o caminho de dados: instruções de comprimento fixo, banco de registradores grande e uniforme, e o princípio load-store (apenas instruções de load e store acessam a memória; todas as operações aritméticas são feita entre registradores). Essa simplicidade permitiu pipelines mais profundos e velocidades de clock mais altas.

Os processadores modernos chegaram a caminhos de dados extraordinariamente complexos: execução fora de ordem (onde o hardware decide executar instruções em ordem diferente da programada para maximizar o uso das unidades funcionais), renaming de registradores (onde o hardware mapeia os registradores visíveis da ISA para um pool muito maior de registradores físicos), e múltiplas unidades de execução em paralelo. Um Intel Core i9 moderno tem centenas de unidades funcionais e pode executar dezenas de operações por ciclo de clock.

O PIC18F4550, com seu caminho de dados modestos e pipeline de dois estágios, representa um ponto muito diferente nesse espectro — deliberadamente simples para atender seus requisitos de custo, consumo de energia e previsibilidade de timing. E é exatamente por essa simplicidade que ele é um veículo de aprendizado tão eficaz: todos os conceitos que você estudou aqui são visíveis e verificáveis, sem camadas de complexidade que obscurecem o funcionamento fundamental.


Referências e Leituras Complementares

Para aprofundar os conceitos deste módulo, os seguintes recursos são especialmente recomendados:

O Datasheet do PIC18F4550 (disponível no site da Microchip) é a referência primária para toda a arquitetura do processador que você está usando. O Capítulo 4 descreve os registradores de memória de dados (GPRs, SFRs, bancos). O Capítulo 3 descreve a memória de programa e o Program Counter. O Capítulo 5 apresenta o mapa completo dos SFRs.

Patterson e Hennessy — “Computer Organization and Design” (disponível em português como “Organização e Projeto de Computadores”): os Capítulos 4.1 a 4.4 descrevem o caminho de dados monociclo com profundidade e rigor, usando a ISA MIPS como exemplo. Os conceitos são diretamente aplicáveis ao que você estudou aqui com o PIC18F4550.

Stallings — “Arquitetura e Organização de Computadores”: os Capítulos 11 e 12 cobrem a organização do processador, o caminho de dados e a unidade de controle com abordagem mais tradicional e igualmente sólida.

O manual do compilador XC8 (disponível no site da Microchip) explica como o compilador aloca variáveis nos bancos de memória e como usar atributos para controlar a alocação. A seção sobre “Memory Models” é particularmente relevante para as otimizações do Projeto Integrador.