Módulo 1: Fundamentos de Arquitetura e Organização de Computadores

Esta é a versão enxuta do Módulo 1, pensada para você revisar antes da aula, durante deslocamentos ou na véspera de uma avaliação. Mantenho aqui os conceitos, diagramas e exemplos indispensáveis; o livro do módulo continua sendo a fonte completa para aprofundamento.

O Problema Que Abre o Semestre

Provocação inicial: dois processadores Intel executando exatamente o mesmo binário x86, sem alterar uma linha de código, podem diferir em cinco vezes no mesmo benchmark. Se ambos rodam o mesmo programa, como isso é possível? A resposta exige separar duas palavras que a literatura técnica costuma misturar: arquitetura e organização. Este módulo constrói essa separação e apresenta o microcontrolador que será seu companheiro de bancada nas próximas quinze semanas: o PIC18F4550 instalado no KIT ACEPIC PRO V8.2.

O caminho tem cinco etapas: distinção arquitetura/organização com critério prático; reconstrução histórica em cinco gerações e a controvérsia RISC versus CISC; os modelos Von Neumann e Harvard, com a variante modificada do PIC; o próprio PIC18F4550 como estudo de caso; e o vocabulário quantitativo de desempenho — tempo de execução, CPI, IPC, leis de Amdahl e Gustafson. Ao final, conectamos cada peça com as três tarefas do Projeto Integrador.

flowchart TB
    subgraph CONC["Fundamentos conceituais"]
        AO["Arquitetura × Organização"]
        HIST["5 gerações<br/>RISC × CISC"]
        VN["Von Neumann"]
        HV["Harvard / Harvard mod."]
    end

    subgraph CASO["Estudo de caso e métricas"]
        PIC["PIC18F4550<br/>(blocos, ciclo, pipeline)"]
        MET["Métricas:<br/>T = N·CPI·Tcy<br/>IPC, MIPS, Amdahl, Gustafson"]
    end

    subgraph PRAT["Prática (Projeto Integrador)"]
        T1["Tarefa 1<br/>Ambiente + LED"]
        T2["Tarefa 2<br/>Quadro Arq/Org<br/>Harvard mod no .map"]
        T3["Tarefa 3<br/>Medição de tempo<br/>no osciloscópio"]
    end

    CONC --> CASO --> PRAT
    PRAT --> M2["Módulo 02:<br/>Representação de Dados"]
Figura 1: Mapa do percurso do módulo: dos fundamentos conceituais à prática no kit.

Arquitetura e Organização

A arquitetura de um sistema computacional é o conjunto de atributos visíveis ao programador: conjunto de instruções, registradores nomeáveis, modelo e modos de endereçamento de memória, mecanismos de exceção, tipos primitivos e modelo de E/S. É o contrato público entre quem escreve o código e quem projeta o chip. A organização, por sua vez, é o conjunto de decisões de implementação que realizam esse contrato: caminho de dados interno, profundidade de pipeline, hierarquia de cache, frequências, larguras físicas de barramento, tecnologia das memórias. Duas organizações distintas podem implementar a mesma arquitetura produzindo desempenhos, consumos e custos radicalmente diferentes, sem que um único programa correto consiga distingui-las pelo resultado lógico.

flowchart TB
    P["Programador<br/>(código C, ASM)"]
    A["Arquitetura<br/>ISA, registradores, modelo de memória,<br/>modos de endereçamento, exceções"]
    O["Organização<br/>Pipeline, caches, ULA, barramentos,<br/>frequência, tecnologia de memória"]
    H["Silício<br/>Transistores, geometria, materiais"]

    P -->|"escreve programa"| A
    A -.->|"contrato visível"| P
    A --> O
    O --> H
    H -.->|"desempenho percebido"| P
Figura 2: As duas camadas: o programador só enxerga a arquitetura; a organização fica oculta atrás do contrato.

A separação não é decorativa. Foi a IBM, em 1964, com a família System/360, quem primeiro definiu uma única arquitetura implementada por modelos com organizações deliberadamente distintas. O mesmo programa rodava em todos, dos modelos baratos aos topos de linha. Nasceu ali a noção moderna de compatibilidade de família.

Para uso prático, ofereço a prova do programador: se a alteração de um detalhe muda o resultado lógico de algum programa correto, esse detalhe é arquitetura; se só afeta tempo, consumo ou custo, é organização. Aplique ao Intel Core i9-13900K e ao Intel Core i3-13100. Ambos implementam x86-64 com SSE, AVX e AVX2; o mesmo binário roda em ambos com os mesmos registradores, instruções e modelo de memória virtual. Arquiteturalmente, equivalentes. A organização é radicalmente diferente: núcleos de desempenho e eficiência no i9, três níveis de cache com dezenas de megabytes, mais de 5 GHz, pipeline com mais de quinze estágios e dezenas de unidades de execução; o i3 tem menos de tudo. O mesmo benchmark roda muitas vezes mais rápido no i9 — eis a resposta da provocação inicial.

Antes de seguir, pergunte-se: se trocássemos a SRAM de 2 KB do PIC18F4550 por uma SRAM de 8 KB sem alterar o mapa de endereçamento visível ao programador, isso seria mudança de arquitetura ou de organização? Responder essa pergunta corretamente é o teste que valida se esta seção foi sedimentada.

Existe uma camada intermediária, a microarquitetura, que aparecerá nos Módulos 04 e 05. Ela vive dentro da organização: descreve como o processador implementa internamente a arquitetura — estrutura do pipeline, execução fora de ordem, forma da unidade de controle — sem descer ao nível de transistores. Cuidado com a literatura comercial: “arquitetura Intel Skylake” ou “arquitetura Apple M2” descrevem, no rigor que adotamos, microarquiteturas dentro das famílias x86-64 e ARMv8-A.

Cinco Gerações de Computadores

Não estudo história por erudição; estudo porque cada inovação arquitetural surgiu como resposta a uma limitação tecnológica concreta, e compreender a limitação é frequentemente a única forma de entender por que a decisão de projeto foi tomada como foi.

timeline
    title Cinco gerações de computadores
    1940-1955 : 1ª geração<br/>Válvulas termiônicas<br/>ENIAC, EDVAC<br/>programa armazenado
    1955-1965 : 2ª geração<br/>Transistor discreto<br/>IBM 7090, PDP-1<br/>FORTRAN, COBOL
    1965-1971 : 3ª geração<br/>CI SSI e MSI<br/>IBM System/360<br/>família arquitetural
    1971-1980 : 4ª geração<br/>Microprocessador<br/>Intel 4004, 8086<br/>computador pessoal
    1980-hoje : 5ª geração<br/>VLSI, computação ubíqua<br/>x86, ARM, PIC18<br/>RISC × CISC
Figura 3: As cinco gerações segundo a tradição da literatura clássica.

A pré-história começa com a Máquina Analítica de Charles Babbage e com as notas de Ada Lovelace, em meados do século XIX, descrevendo o primeiro algoritmo escrito para um computador. A primeira geração (1940–1955) usa válvulas termiônicas: o ENIAC, com dezessete mil tubos, era programado por reconexão manual de cabos. Nessa geração, von Neumann formulou no relatório do EDVAC, em 1945, o conceito de programa armazenado.

A segunda geração trouxe os transistores discretos e linguagens como FORTRAN e COBOL. A terceira, dos anos 1960 ao início dos 1970, os circuitos integrados, o IBM System/360 e a microprogramação. A quarta começa em 1971 com o Intel 4004, primeiro microprocessador em pastilha única, e viabiliza o computador pessoal. A quinta geração, dos anos 1980 em diante, é a da integração em altíssima escala (VLSI) e da computação ubíqua: smartphone, sensor inteligente, microcontrolador embarcado. O PIC18F4550 — projetado pela Microchip em torno de 2007, ~30 000 transistores, 32 KB de Flash, 2 KB de RAM, dezenas de periféricos — é representante típico. Cada geração não substituiu as anteriores; aprimorou-as por uma camada adicional. O chip que você vai gravar executa internamente um modelo de Von Neumann modificado formulado em 1945.

Uma controvérsia das décadas de 1980 a 2000 merece destaque: RISC versus CISC. Processadores CISC (VAX-11, x86) adotavam conjuntos extensos de instruções e múltiplos modos de endereçamento, esperando reduzir o trabalho do compilador. Patterson em Berkeley e Hennessy em Stanford observaram empiricamente que só um pequeno subconjunto das instruções CISC era usado pelos compiladores; propuseram o contrário: poucas instruções simples, tamanho fixo, idealmente um ciclo cada, acesso a memória restrito a load e store. O PIC18F4550 é híbrido: tamanho fixo de 16 bits (típico RISC), mas 75 instruções, várias combinando acesso à memória com aritmética como ADDWF (típico CISC). O desfecho histórico foi pragmático: processadores x86 modernos traduzem internamente em micro-operações RISC; ARM e RISC-V acumularam extensões que os aproximam do CISC em densidade.

O Modelo de Von Neumann

A invenção que justifica nomear um modelo arquitetural inteiro em homenagem a John von Neumann foi a do programa armazenado: instruções representadas como números, armazenadas na mesma memória que armazena os dados, lidas pelo processador da mesma forma que dados são lidos. Essa decisão tem três consequências profundas: o programa pode ser modificado tão facilmente quanto qualquer outro dado, abrindo caminho para carregamento dinâmico de programas e compiladores; o hardware necessário para acessar instruções é o mesmo necessário para acessar dados, economizando circuitos; e abre-se a possibilidade — hoje desencorajada por segurança — de programas que se modificam a si mesmos.

flowchart LR
    subgraph CPU["CPU"]
        UC["Unidade<br/>de Controle"]
        ULA["ULA"]
        REG["Registradores<br/>PC, IR, ACC"]
    end
    MEM["Memória Principal<br/>instruções + dados<br/>(mesma)"]
    IO["Subsistema<br/>de E/S"]

    CPU <-->|"barramento único<br/>endereço/dado/controle"| MEM
    CPU <--> IO
Figura 4: Modelo de Von Neumann: memória única, barramento único, serialização forçada entre busca de instrução e acesso a dado.

O modelo tem cinco subsistemas: unidade de controle, unidade lógica e aritmética, registradores de trabalho, memória principal e subsistema de E/S. A execução segue o ciclo de busca-decodificação-execução. O contador de programa (PC) contém o endereço da próxima instrução. A unidade de controle envia esse endereço pelo barramento; a memória responde com a instrução, que é colocada no registrador de instrução (IR); o IR é decodificado e os sinais correspondentes coordenam a execução. Ao final, o PC é incrementado (ou alterado por desvio) e o ciclo recomeça. É a base do funcionamento de quase todos os processadores que existiram, do EDVAC ao PIC18F4550 do seu kit.

A elegância do modelo esconde uma fragilidade. Como instruções e dados compartilham o mesmo barramento, a CPU não pode buscar uma instrução e ler um dado no mesmo instante: as duas operações precisam ser serializadas. Esse fenômeno, descrito por John Backus na sua palestra do prêmio Turing em 1977, ficou conhecido como gargalo de Von Neumann. Em primeira aproximação, se o tempo interno da CPU por instrução é Tp e o tempo de acesso à memória é Tm, o tempo efetivo por instrução satisfaz

\tau \geq \tau_p + 2 \, \tau_m,

porque cada instrução exige pelo menos uma busca da instrução em si e um acesso ao dado que ela manipula. Quando Tm é muito maior que Tp, a CPU passa a maior parte do tempo esperando a memória — situação que hoje atende pelo nome de memory wall. A solução clássica nos computadores de propósito geral foi a hierarquia de caches que você estudará no Módulo 09. A solução adotada pela maioria dos microcontroladores foi diferente e dá nome à próxima seção.

Harvard e Harvard Modificada

O nome “Harvard” provém do computador Mark I, construído por Howard Aiken em colaboração entre IBM e Universidade de Harvard, operacional em 1944. Usava fitas de papel perfurado para instruções e contadores eletromecânicos para dados, em sistemas fisicamente separados. Décadas depois, esse esquema histórico forneceu o nome para uma classe de processadores que mantém instruções e dados em memórias fisicamente distintas, com barramentos independentes.

flowchart LR
    MI["Memória de<br/>Instruções<br/>(Flash 32 KB)"]
    CPU["CPU"]
    MD["Memória de<br/>Dados<br/>(SRAM 2 KB)"]
    EE["EEPROM<br/>256 bytes"]

    MI -->|"barramento de<br/>instruções 16 bits"| CPU
    CPU <-->|"barramento de<br/>dados 8 bits"| MD
    MI -.->|"TBLRD via TBLPTR<br/>(Harvard modificada)"| CPU
    CPU <-.->|"EECON1/EECON2<br/>(protocolo)"| EE
Figura 5: Harvard modificada do PIC18F4550: dois espaços principais, duas vias paralelas, mais a EEPROM como terceiro espaço com protocolo próprio.

O princípio é direto: instruções e dados ocupam espaços de endereçamento separados, com barramentos físicos próprios. Em um mesmo ciclo, o processador pode buscar uma instrução e simultaneamente ler ou escrever um dado. A largura dos dois barramentos pode ser diferente — no PIC18, instruções têm 16 bits e dados têm 8. As tecnologias de memória também podem diferir: Flash não volátil para instruções, SRAM volátil rápida para dados. A vantagem mais visível é o paralelismo de acesso. Processadores Harvard alcançam, em condições favoráveis, uma instrução executada por ciclo de relógio sem precisar de cache interna sofisticada.

A Harvard pura tem uma limitação prática: constantes e tabelas grandes — strings, tabelas de senos, vetores de calibração — não cabem confortavelmente na memória de dados. A solução do PIC18 e da maioria dos microcontroladores modernos é a Harvard modificada: mantém-se a separação de memórias e barramentos, mas a ISA inclui instruções específicas que permitem ler bytes da memória de instruções como se fossem dados, com latência ligeiramente maior. No PIC18, essas instruções são TBLRD*, TBLRD*+, TBLRD*- e TBLRD+*, operando com o ponteiro TBLPTR; o byte lido aparece em TABLAT. Ao declarar static const uint8_t tabela[] = { ... } em C, o XC8 aloca a tabela em Flash e emite automaticamente a sequência MOVLW/ADDWF/TBLRD* ao acessar tabela[i]. Para você, parece um acesso ordinário a vetor; para o hardware, é uma travessia de fronteira entre dois espaços de endereçamento que, em Harvard pura, seria proibida.

O PIC18 dispõe ainda de um terceiro espaço: a EEPROM de 256 bytes, para dados persistentes em pequena escala — calibrações, contadores não voláteis, identificadores únicos. O acesso é mediado por EECON1 e EECON2, com protocolo de bytes mágicos que evita escritas acidentais. A EEPROM tolera centenas de milhares de ciclos de escrita por célula, contra dezenas de milhares da Flash.

Três modelos lado a lado

Aspecto Von Neumann Harvard pura Harvard modificada (PIC18)
Espaços de endereçamento Um único Dois disjuntos Dois (mais EEPROM), com acesso cruzado limitado
Barramentos físicos Um Dois independentes Dois (instruções 16 bits, dados 8 bits)
Acesso paralelo busca/dado Não Sim Sim
Constantes literais grandes Vivem na RAM Vivem na RAM (limitação) Vivem em Flash, lidas via TBLRD
Programas auto-modificáveis Possíveis Impossíveis na ISA pura Possíveis via escrita controlada na Flash
Representantes típicos x86 (arquitetural), CPUs de propósito geral DSPs clássicos PIC, AVR, ARM Cortex-M, MSP430

Um ponto que costuma surpreender: o x86 moderno, do ponto de vista do programador, é uma arquitetura de Von Neumann — uma única memória vista pelo programa — mas internamente as caches L1 são separadas em cache de instruções e cache de dados, materializando uma organização de inspiração harvardiana. A arquitetura é Von Neumann; a organização é mista. Esse exemplo ilustra, sozinho, a potência da distinção que construímos na seção anterior.

O PIC18F4550 como Estudo de Caso

O microcontrolador foi escolhido por quatro razões. A regularidade ortogonal — 75 instruções, quase todas de um ciclo, codificação fixa de 16 bits em poucas famílias — permite estudar o ISA inteiro num único módulo. O acesso direto aos periféricos (ADC, USB, UART, SPI, I²C, comparadores, timers) integra-se ao chip e é documentado num único datasheet. A previsibilidade absoluta de timing, sem caches nem execução fora de ordem, deixa o tempo de execução exatamente calculável. Por fim, o custo e a robustez do KIT ACEPIC PRO V8.2 toleram os erros típicos de um estudante aprendendo eletrônica embarcada.

flowchart TB
    subgraph Nucleo["Núcleo do PIC18F4550"]
        UC["Unidade<br/>de Controle"]
        ULA["ULA 8 bits"]
        W["WREG"]
        ST["STATUS<br/>C, DC, Z, OV, N"]
    end

    Flash["Flash 32 KB<br/>(16 K palavras<br/>de 16 bits)"]
    SRAM["SRAM 2 KB<br/>(16 bancos +<br/>Access Bank)"]
    EE["EEPROM<br/>256 bytes"]

    subgraph Perif["Periféricos integrados"]
        USB["USB 2.0"]
        UART["EUSART"]
        MSSP["MSSP<br/>(I²C/SPI)"]
        ADC["ADC 10 bits<br/>13 canais"]
        TIM["Timers<br/>+ CCP/PWM"]
        IO["35 pinos I/O<br/>PORTA..PORTE"]
    end

    CK["Oscilador externo<br/>8 MHz → ÷4 → Fcy = 2 MHz<br/>+ PLL para USB"]

    Flash --> Nucleo
    Nucleo <--> SRAM
    Nucleo <-.-> EE
    Nucleo <--> Perif
    CK --> Nucleo
Figura 6: Anatomia em blocos do PIC18F4550: núcleo com ULA, W e STATUS, três memórias, periféricos integrados e subsistema de clock.

A CPU abriga a unidade de controle, a ULA de 8 bits, o registrador WREG (W) e o registrador STATUS com os cinco flags fundamentais — Carry, Digit Carry, Zero, Overflow e Negative. A Flash de 32 KB armazena 16 K palavras de 16 bits, reprogramável in-circuit. A SRAM de 2 KB se organiza em dezesseis bancos de 256 bytes selecionados pelo BSR, com o Access Bank combinando os 96 bytes iniciais da RAM com os 160 bytes superiores onde residem os SFRs. Os periféricos são fartos: dois MSSP (I²C/SPI), EUSART, USB 2.0 full-speed, ADC de 10 bits com até 13 canais, três comparadores, quatro timers, dois módulos CCP e 35 pinos de I/O nos portos A, B, C, D e E.

Vamos exercitar a prova do programador sobre esse chip. O conjunto de 75 instruções, o WREG, os flags de STATUS, os três espaços de memória com seus mapeamentos e os SFRs como TRISx, LATx, ADCONx — tudo isso é arquitetura, porque o seu código depende deles. O pipeline de dois estágios, o Access Bank, a largura física do barramento de dados de 8 bits, o cristal externo e a divisão por quatro, a PLL do USB, a tecnologia das células de memória — tudo isso é organização. Esse quadro é a base da Tarefa 2 do Projeto Integrador.

O ciclo de instrução do PIC18 compacta as cinco fases conceituais — busca, decodificação, execução, acesso à memória e escrita do resultado — em apenas dois estágios físicos: fetch e execute. É a arquitetura Harvard que permite essa compactação, pois a CPU consegue buscar a próxima instrução pelo barramento de instruções enquanto a atual ainda está sendo executada via barramento de dados.

flowchart LR
    subgraph C1["Ciclo 1"]
        F1["Fetch I1"]
    end
    subgraph C2["Ciclo 2"]
        E1["Execute I1"]
        F2["Fetch I2"]
    end
    subgraph C3["Ciclo 3"]
        E2["Execute I2"]
        F3["Fetch I3"]
    end
    subgraph C4["Ciclo 4"]
        E3["Execute I3"]
        F4["Fetch I4"]
    end

    F1 --> E1
    F2 --> E2
    F3 --> E3
    F4 --> E4["Execute I4"]
Figura 7: Pipeline de dois estágios em regime permanente: uma instrução concluída por ciclo de máquina.

Depois de uma latência inicial de um ciclo, o processador conclui uma instrução por ciclo de máquina. A exceção fica por conta das instruções que alteram o PC — todas as variantes de GOTO, CALL, BRA quando tomadas, RETURN, RETFIE e os testes condicionais BTFSS/BTFSC que causam skip. Nessas, o prefetch que já trouxe a instrução errada precisa ser descartado, custando um ciclo adicional. Em outras palavras, o CPI dessas instruções é 2 em vez de 1. Essa única observação — desvios tomados custam dois ciclos enquanto a maioria custa um — sustenta toda a análise de desempenho que você fará neste semestre.

Métricas de Desempenho

Quando alguém afirma que um processador é “duas vezes mais rápido” do que outro, pergunte: em que carga de trabalho, com qual compilador, sob qual frequência e medindo qual quantidade? Sem qualificação, a expressão é vazia. Esta seção fornece o vocabulário para conversar sobre desempenho com precisão.

A métrica fundamental é o tempo de execução — duração em segundos entre o início e a conclusão da tarefa. Decompomos esse tempo em três fatores:

T = N_{\text{instr}} \cdot \text{CPI} \cdot T_{cy},

onde N é o número total de instruções executadas, CPI é o número médio de ciclos de máquina por instrução (Cycles Per Instruction) e Tcy é o período de um ciclo de máquina. A elegância da decomposição está em que cada fator depende de uma camada distinta: N depende do algoritmo e do compilador; CPI depende da arquitetura, da microarquitetura e da mistura de instruções; Tcy depende exclusivamente da organização — tecnologia, projeto físico, refrigeração. Otimizar desempenho é atacar simultaneamente esses três fatores.

flowchart LR
    XT["Cristal externo<br/>Fosc = 8 MHz"]
    DIV["Divisor<br/>÷ 4"]
    FCY["Clock de máquina<br/>Fcy = 2 MHz<br/>Tcy = 500 ns"]
    CPU["CPU<br/>(Q1, Q2, Q3, Q4)"]

    XT --> DIV --> FCY --> CPU
Figura 8: Cadeia de clock do PIC18: o cristal externo Fosc é dividido por 4 para gerar o clock de máquina Fcy.

No PIC18, há um detalhe que você precisa memorizar com firmeza: o cristal externo gera a frequência Fosc, mas internamente um divisor por quatro produz o clock de máquina Fcy. A relação é Fcy = Fosc / 4. Para o cristal de 8 MHz típico do KIT ACEPIC PRO V8.2, Fcy vale 2 MHz, e portanto Tcy = 500 ns. A confusão mais frequente nesta disciplina é trocar Fosc por Fcy; ao errar essa divisão por quatro, você produz uma previsão teórica quatro vezes menor do que o valor medido. Anote esse fator: ele será aplicado em todas as análises de tempo do semestre.

Para uma mistura de instruções com frequências relativas f1, f2, …, fn e CPIs individuais c1, c2, …, cn, o CPI médio vale

\text{CPI}_{\text{médio}} = \sum_{i=1}^{n} f_i \, c_i.

Exemplo concreto: um programa com 90% de instruções de CPI 1 e 10% de desvios tomados (CPI 2) tem CPI médio 0,90 · 1 + 0,10 · 2 = 1,10. A 8 MHz com 10 000 instruções, o tempo total é 10 000 · 1,10 · 500 ns ≈ 5,5 ms.

O throughput generaliza a métrica para sistemas com muitas tarefas em paralelo. Em processadores, mede-se em IPC (Instructions Per Cycle) ou MIPS. A relação é IPC = 1/CPI. No PIC18, em código sem desvios tomados, o IPC fica próximo de 1 em regime de pipeline cheio. Superescalares modernos atingem IPC maior que 1 — tema do Módulo 15. As métricas MIPS e MFLOPS têm armadilha: dependem da mistura de instruções, e uma máquina com mais instruções “fáceis” pode apresentar MIPS maior sem ser, em sentido prático, mais rápida.

Para tornar o efeito do CPI palpável, considere o seguinte laço de soma — variantes desse padrão reaparecerão ao longo do semestre.

01_cpi_demo.c
/*
 * Modulo 01 - Demonstracao do conceito de CPI (Cycles Per Instruction).
 * Plataforma: PIC18F4550 + KIT ACEPIC PRO V8.2.
 *
 * Duas funcoes realizam exatamente a mesma soma de 100 valores armazenados
 * em um vetor, mas com numero de instrucoes e mistura de CPIs diferentes.
 * Compilando com XC8 e inspecionando o .lst, voce conta as instrucoes
 * geradas e calcula o CPI medio de cada versao. Esse exercicio fixa a
 * relacao T = N_instr * CPI_medio * Tcy estudada no capitulo.
 */

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

#pragma config FOSC  = XT_XT
#pragma config WDT   = OFF
#pragma config LVP   = OFF
#pragma config MCLRE = ON
#pragma config PBADEN = OFF

#define _XTAL_FREQ 8000000UL

static const uint8_t valores[100] = {
    /* 100 bytes constantes; ficam na Flash, lidos via TBLRD */
    1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14,15,16,17,18,19,20,
   21,22,23,24,25,26,27,28,29,30, 31,32,33,34,35,36,37,38,39,40,
   41,42,43,44,45,46,47,48,49,50, 51,52,53,54,55,56,57,58,59,60,
   61,62,63,64,65,66,67,68,69,70, 71,72,73,74,75,76,77,78,79,80,
   81,82,83,84,85,86,87,88,89,90, 91,92,93,94,95,96,97,98,99,100
};

static volatile uint16_t soma;

/* Versao 1: laço explícito. Muitas instruções de controle (incremento
 * de índice, comparacao, desvio condicional). CPI medio mais alto pelo
 * peso dos desvios tomados. */
static void soma_v1(void)
{
    soma = 0;
    for (uint8_t i = 0; i < 100; i++) {
        soma = (uint16_t)(soma + valores[i]);
    }
}

/* Versao 2: laco desenrolado em blocos de 4. Reduz a fracao de instrucoes
 * de controle e, em consequencia, derruba o CPI medio mesmo executando
 * praticamente o mesmo numero de somas. */
static void soma_v2(void)
{
    soma = 0;
    for (uint8_t i = 0; i < 100; i += 4) {
        soma = (uint16_t)(soma + valores[i]);
        soma = (uint16_t)(soma + valores[i + 1]);
        soma = (uint16_t)(soma + valores[i + 2]);
        soma = (uint16_t)(soma + valores[i + 3]);
    }
}

void main(void)
{
    TRISD = 0x00;
    LATD  = 0x00;

    while (1) {
        LATDbits.LATD0 = 1;
        soma_v1();
        LATDbits.LATD0 = 0;

        __delay_ms(20);

        LATDbits.LATD1 = 1;
        soma_v2();
        LATDbits.LATD1 = 0;

        __delay_ms(200);
    }
}
01_cpi_demo.asm
; Modulo 01 - Demonstracao de CPI em assembly puro.
; Plataforma: PIC18F4550 + ACEPIC PRO V8.2.
;
; O programa realiza dois trechos de codigo equivalentes em efeito,
; mas com numero distinto de instrucoes e CPI medio distinto. RD0
; delimita o trecho 1 (laço); RD1 delimita o trecho 2 (desenrolado).
; Medindo a largura dos pulsos com osciloscopio, o estudante observa
; diretamente a relacao T = N_instr * CPI_medio * Tcy.

    LIST    P=18F4550
    #include <p18f4550.inc>

    CONFIG  FOSC=XT_XT, WDT=OFF, LVP=OFF, MCLRE=ON, PBADEN=OFF

    CBLOCK  0x00
        ACC_L
        ACC_H
        IDX
        D1, D2, D3
    ENDC

    ORG     0x0000
    GOTO    INICIO

    ORG     0x0040
INICIO:
    CLRF    TRISD, ACCESS
    CLRF    LATD,  ACCESS

LOOP_PRINC:
    ; --- Trecho 1: laço de 100 incrementos ----------------------
    BSF     LATD, 0, ACCESS
    CLRF    ACC_L, ACCESS
    CLRF    ACC_H, ACCESS
    MOVLW   d'100'
    MOVWF   IDX, ACCESS
LACO_V1:
    MOVLW   1
    ADDWF   ACC_L, F, ACCESS
    MOVLW   0
    ADDWFC  ACC_H, F, ACCESS
    DECFSZ  IDX, F, ACCESS
    BRA     LACO_V1
    BCF     LATD, 0, ACCESS

    CALL    DELAY_CURTO

    ; --- Trecho 2: laço desenrolado em blocos de 4 --------------
    BSF     LATD, 1, ACCESS
    CLRF    ACC_L, ACCESS
    CLRF    ACC_H, ACCESS
    MOVLW   d'25'                  ; 25 iteracoes de 4 somas cada
    MOVWF   IDX, ACCESS
LACO_V2:
    MOVLW   1
    ADDWF   ACC_L, F, ACCESS
    ADDWFC  ACC_H, F, ACCESS
    MOVLW   1
    ADDWF   ACC_L, F, ACCESS
    ADDWFC  ACC_H, F, ACCESS
    MOVLW   1
    ADDWF   ACC_L, F, ACCESS
    ADDWFC  ACC_H, F, ACCESS
    MOVLW   1
    ADDWF   ACC_L, F, ACCESS
    ADDWFC  ACC_H, F, ACCESS
    DECFSZ  IDX, F, ACCESS
    BRA     LACO_V2
    BCF     LATD, 1, ACCESS

    CALL    DELAY_LONGO
    BRA     LOOP_PRINC

DELAY_CURTO:
    MOVLW   d'10'
    MOVWF   D3, ACCESS
DC_3:
    MOVLW   d'200'
    MOVWF   D2, ACCESS
DC_2:
    MOVLW   d'20'
    MOVWF   D1, ACCESS
DC_1:
    DECFSZ  D1, F, ACCESS
    BRA     DC_1
    DECFSZ  D2, F, ACCESS
    BRA     DC_2
    DECFSZ  D3, F, ACCESS
    BRA     DC_3
    RETURN

DELAY_LONGO:
    MOVLW   d'100'
    MOVWF   D3, ACCESS
DL_3:
    MOVLW   d'200'
    MOVWF   D2, ACCESS
DL_2:
    MOVLW   d'20'
    MOVWF   D1, ACCESS
DL_1:
    DECFSZ  D1, F, ACCESS
    BRA     DL_1
    DECFSZ  D2, F, ACCESS
    BRA     DL_2
    DECFSZ  D3, F, ACCESS
    BRA     DL_3
    RETURN

    END

As duas versões fazem o mesmo trabalho útil, mas a fração de instruções de controle é diferente, e portanto o CPI médio difere entre elas. Medindo a largura do pulso em RD0 com osciloscópio, você confirma diretamente a previsão teórica calculada a partir do datasheet.

A Lei de Amdahl responde a uma pergunta natural: suponha que você identifique, em um programa, uma fração f do tempo de execução que pode ser acelerada por um fator k. Qual o ganho global S, ou speedup, que se obtém? A resposta foi formulada por Gene Amdahl em 1967:

S = \frac{1}{(1-f) + \dfrac{f}{k}}.

No limite em que k tende ao infinito, o speedup máximo vale 1 dividido por 1 menos f, independentemente de quão rápida a porção acelerada se torne.

flowchart LR
    T0["Tempo original<br/>T = 1"]
    F["Fração f<br/>acelerável"]
    NF["Fração (1−f)<br/>inalterada"]
    K["Aceleração<br/>por fator k"]
    S["Speedup global<br/>S = 1 / ((1−f) + f/k)"]
    LIM["Limite k → ∞<br/>S_max = 1 / (1−f)"]

    T0 --> F
    T0 --> NF
    F --> K
    K --> S
    NF --> S
    S --> LIM
Figura 9: Geometria da Lei de Amdahl: a fração não acelerada estabelece o teto do speedup global.

A lição é desconcertante. Acelerar infinitamente 90% do tempo produz speedup global de apenas dez vezes, porque os 10% restantes continuam consumindo o mesmo tempo absoluto. Acelerar por fator 10 uma fração de 50% do tempo dá speedup de apenas 1,82 vezes, não 5,5. Otimização vale a pena onde o programa gasta tempo, e o ganho global é tetado pela fração não otimizada. Antes de qualquer otimização, pergunte: que fração do tempo total essa rotina consome?

A Lei de Gustafson, de 1988, complementa Amdahl. Na prática, à medida que as máquinas ficam mais rápidas, os usuários aumentam o tamanho dos problemas, e a fração paralelizável cresce mais que a sequencial. Sob essa hipótese, o speedup escalado vale (1 − f) + k · f, crescendo linearmente em k. Não há contradição: Amdahl descreve strong scaling (problema fixo, mais recursos); Gustafson, weak scaling (problema cresce com os recursos). Para sistemas embarcados, em que o problema é dado pela aplicação — controlar um motor, ler um sensor a taxa fixa — Amdahl é mais relevante. Para data centers, Gustafson costuma ser mais aderente.

Se um programa gasta 30% do tempo em E/S sequencial obrigatória e 70% em um laço puramente computacional, qual o speedup máximo teórico possível mesmo paralelizando o laço infinitamente? Faça a conta antes de avançar; este é exatamente o tipo de pergunta que aparecerá em prova.

Da Teoria ao Kit: o Projeto Integrador

A teoria serviria de pouco sem aferição empírica. A cadeia de ferramentas que conecta o seu código C ao silício é uma sequência de cinco blocos.

flowchart LR
    SRC["Código-fonte<br/>(.c / .asm)<br/>MPLAB X IDE"]
    XC8["XC8 (C)<br/>ou pic-as (ASM)"]
    LINK["Linker<br/>(.elf)"]
    HEX["Imagem<br/>Intel HEX (.hex)"]
    CHIP["PIC18F4550<br/>(Flash gravada)"]

    SRC --> XC8 --> LINK --> HEX
    HEX -->|"bootloader<br/>via USB"| CHIP
    HEX -->|"PICkit<br/>via ICSP"| CHIP
Figura 10: Cadeia de ferramentas do PIC18: do código-fonte ao silício.

O MPLAB X IDE é o ambiente integrado. O XC8 traduz código C em assembly e código de máquina; o pic-as faz o mesmo a partir de assembly puro. A saída é um .hex no formato Intel HEX, gravado na Flash pelo bootloader residente (via USB com AN1310 ou ACEPIC Terminal) ou por um PICkit conectado ao header ICSP.

A primeira tarefa do Projeto Integrador pede configurar todo esse ambiente e gravar um programa que pisca os oito LEDs do kit em sequência. O programa é simples — PORTD como saída, varredura L1 a L8 com 500 ms entre eles — mas o ponto pedagógico está na cadeia inteira que precisa funcionar: driver do CH340G, versão compatível do MPLAB X, XC8 reconhecendo o PIC18F4550, jumpers na posição correta, bootloader íntegro. Cada elo é uma oportunidade de falha, e lidar com elas é parte da experiência do módulo.

01_blink_leds.c
/*
 * Modulo 01 - Tarefa 1: programa de validacao do ambiente.
 * Plataforma: PIC18F4550 + KIT ACEPIC PRO V8.2.
 * Cristal externo de 8 MHz (XT) -> Fosc = 8 MHz, Tcy = 0,5 us.
 * LEDs L1..L8 ligados em PORTD (RD0..RD7); jumper LEDS = fechado.
 * Compilador: Microchip XC8.
 */

#include <xc.h>

// Configuracao de fusiveis para o cristal de 8 MHz do kit, WDT desligado,
// LVP desligado, MCLR habilitado, brown-out desligado.
#pragma config FOSC = XT_XT
#pragma config WDT  = OFF
#pragma config LVP  = OFF
#pragma config MCLRE = ON
#pragma config PBADEN = OFF   // PORTB em modo digital apos reset

#define _XTAL_FREQ 8000000UL

static void delay_500ms(void)
{
    // Atraso baseado em macro do XC8, equivalente a 500 ms.
    __delay_ms(500);
}

void main(void)
{
    TRISD = 0x00;   // PORTD inteiro como saida (LEDs L1..L8)
    LATD  = 0x00;

    while (1) {
        // Acende um LED de cada vez, varrendo da direita para a esquerda.
        for (unsigned char i = 0; i < 8; i++) {
            LATD = (unsigned char)(1u << i);
            delay_500ms();
        }
        LATD = 0x00;
        delay_500ms();
    }
}
01_blink_leds.asm
; Modulo 01 - Tarefa 1: programa de validacao do ambiente em assembly.
; Plataforma: PIC18F4550 + KIT ACEPIC PRO V8.2.
; Cristal externo 8 MHz, Tcy = 0,5 us.
; LEDs L1..L8 em RD0..RD7.
; Montador: MPASMX / pic-as (sintaxe MPASM classica).

    LIST    P=18F4550
    #include <p18f4550.inc>

    CONFIG  FOSC=XT_XT, WDT=OFF, LVP=OFF, MCLRE=ON, PBADEN=OFF

; ---------------- Variaveis em Access Bank ----------------
    CBLOCK  0x00
        MASK        ; padrao atual dos LEDs
        D1, D2, D3  ; contadores do atraso
    ENDC

; ---------------- Vetor de reset ----------------
    ORG     0x0000
    GOTO    INICIO

    ORG     0x0020
INICIO:
    CLRF    TRISD, ACCESS      ; PORTD todo como saida
    CLRF    LATD,  ACCESS
    MOVLW   0x01
    MOVWF   MASK,  ACCESS

LOOP_PRINC:
    MOVF    MASK,  W, ACCESS
    MOVWF   LATD,  ACCESS
    CALL    DELAY_500MS

    ; Desloca a mascara um bit para a esquerda.
    RLNCF   MASK,  F, ACCESS
    ; Se completou 8 posicoes, recomeca com 0x01.
    MOVF    MASK,  W, ACCESS
    BNZ     LOOP_PRINC
    MOVLW   0x01
    MOVWF   MASK,  ACCESS
    BRA     LOOP_PRINC

; ---------------- Atraso por software ----------------
; Aproximadamente 500 ms a Tcy = 0,5 us.
; Loop triplo: D3 * D2 * D1 ciclos = 250 * 200 * 20 ~ 1e6 ciclos -> 500 ms.
DELAY_500MS:
    MOVLW   d'250'
    MOVWF   D3, ACCESS
DL_3:
    MOVLW   d'200'
    MOVWF   D2, ACCESS
DL_2:
    MOVLW   d'20'
    MOVWF   D1, ACCESS
DL_1:
    DECFSZ  D1, F, ACCESS
    BRA     DL_1
    DECFSZ  D2, F, ACCESS
    BRA     DL_2
    DECFSZ  D3, F, ACCESS
    BRA     DL_3
    RETURN

    END

A segunda tarefa pede evidência empírica da Harvard modificada. O programa define uma tabela de oito padrões binários em const uint8_t (alocada em Flash pelo XC8) e uma variável de índice em RAM; a cada iteração, o byte é lido via TBLRD e escrito em LATD. O entregável exige o quadro Arq/Org dos elementos do PIC18F4550 e a inspeção do arquivo .map gerado pelo XC8: ele lista os endereços de cada símbolo, e você verá padroes[] em endereços baixos (Flash, a partir de 0x000000) enquanto a variável de índice ocupa outra faixa (RAM, no Access Bank). Essa observação cristaliza a separação que, no diagrama, parecia apenas conceitual.

A terceira tarefa fecha o ciclo teoria-medição. Você grava um programa que eleva RD0 antes de um trecho instrumentado e o abaixa depois, observa o pulso no osciloscópio, calcula o tempo teórico pela contagem de instruções no datasheet e compara com a medida. O entregável inclui tabela de instruções, cálculo teórico, valor medido, erro relativo e discussão das fontes de incerteza. É a primeira tarefa em que o estudante deixa de ser usuário do microcontrolador e começa a agir como engenheiro: prevê, mede, confronta.

Configuração física do kit: pontos críticos

Quatro pontos do KIT ACEPIC PRO V8.2 merecem atenção logo no primeiro módulo. O jumper LEDS deve estar fechado para que L1 a L8 acendam quando LATD for escrito. O jumper J1 deve estar na posição 1-2, alimentando o chip com 5 V regulados. Os jumpers JP14 do cristal de 8 MHz devem ficar fechados na posição vertical; abertos, o chip recorre ao oscilador interno e o programa parece rodar em frequência errada. O DIP-switch DP2 deve ter as chaves 5 a 8 em OFF, pois controlam os cátodos dos displays de 7 segmentos compartilhados com PORTD. Esses detalhes parecem triviais, mas consomem uma aula inteira quando ignorados.

Há ainda o diário de projeto, um arquivo em Markdown na pasta /docs/diario/ da dupla, atualizado a cada tutoria com data, presentes, atividades, problemas, soluções e planejamento da semana seguinte. Cumpre três funções: continuidade entre sessões, materialização do aprendizado pelo ato de escrever e evidência para a avaliação contínua.

Síntese

Você percorreu, neste módulo, um caminho que partiu de uma distinção aparentemente acadêmica — arquitetura versus organização — e desembocou na configuração física de um microcontrolador real, com tempo de execução medido no osciloscópio. A trajetória passou pela história em cinco gerações, pela controvérsia RISC versus CISC, pelos modelos Von Neumann e Harvard (com a variante modificada do PIC18) e pelas métricas que sustentarão as análises de desempenho dos próximos quatorze módulos.

A pergunta de abertura — como dois processadores com a mesma arquitetura podem diferir cinco vezes em desempenho? — agora tem resposta precisa: arquitetura define o contrato com o programador, organização define o desempenho real. Essa é a tese central do nosso campo e o pré-requisito para tudo que vem adiante.

Antes de virar a página, faça uma autoavaliação honesta. Você deve enunciar as definições operacionais de arquitetura e organização e aplicar a prova do programador a qualquer componente. Deve descrever o ciclo de busca-decodificação-execução e identificar o gargalo de Von Neumann em uma frase. Deve diferenciar Harvard pura e modificada e justificar a escolha da segunda para o PIC18F4550. Deve calcular o tempo de execução pela equação fundamental T igual a N vezes CPI vezes Tcy, sem esquecer da divisão por quatro no clock do PIC18. Deve aplicar a Lei de Amdahl para estimar o speedup máximo em cenários típicos. E deve ter, no diário de projeto, o relato da primeira sessão de tutoria com o LED piscando, o quadro Arq/Org e o pulso medido no osciloscópio.

No Módulo 02, mergulharemos na representação de dados e aritmética computacional: complemento de dois, IEEE 754, circuitos somadores e multiplicadores. Deixo uma pergunta para o intervalo: por que somar dois números de 16 bits em um chip de 8 bits como o PIC18 exige mais de uma instrução, e que registrador especial entra em cena nessa segunda instrução?

Antes da próxima aula, abra o MPLAB X, compile o programa do LED piscando que apresentei nas Tarefas do Projeto Integrador, conecte o osciloscópio em RD0 e meça a largura do pulso. Compare com o valor que você calcular a partir da contagem de instruções e da relação Tcy igual a 4 sobre Fosc. O encontro entre teoria e medição é o ponto de partida do seu trabalho no semestre inteiro.