College Online
0%

Von Neumann e a Máquina Moderna

Módulo 1 · Aula 2 ~12 min de leitura Nível: Introdutório

Vídeo da aula estará disponível em breve

O modelo stored-program

Antes de 1945, computadores eram máquinas de propósito fixo. O ENIAC, por exemplo, era "programado" reconectando cabos e ajustando chaves — um processo que podia levar dias. Cada programa novo exigia reconfiguração física do hardware.

A ideia revolucionária de John von Neumann — publicada no relatório "First Draft of a Report on the EDVAC" (1945) — foi simples mas transformadora: armazenar o programa na mesma memória que os dados. O programa deixa de ser fiação física e passa a ser uma sequência de números na memória, indistinguível dos dados. A máquina lê instruções da memória, decodifica-as, e executa-as — uma de cada vez, sequencialmente.

Essa ideia — o stored-program concept — é o fundamento de praticamente todos os computadores que existem hoje. Do microcontrolador no seu forno de micro-ondas ao supercomputador que treina modelos de IA, todos seguem esse princípio.

i
Nota histórica A atribuição a Von Neumann é controversa. Alan Turing já havia descrito o conceito de máquina universal (1936), e os engenheiros J. Presper Eckert e John Mauchly (criadores do ENIAC) argumentaram que a ideia do stored-program era deles. Von Neumann foi quem a formalizou e publicou primeiro, e o nome ficou. Na academia, chamamos de "arquitetura Von Neumann" independentemente da autoria real.

Os cinco componentes

A arquitetura Von Neumann organiza o computador em cinco componentes fundamentais, conectados por barramentos:

Diagrama — Arquitetura Von Neumann
                    ┌─────────────────────────────────┐
                    │            CPU                   │
                    │  ┌───────────┐  ┌─────────────┐  │
                    │  │    UC     │  │    ULA       │  │
                    │  │ (Unidade  │  │ (Unidade     │  │
                    │  │  de       │←→│  Lógica e    │  │
                    │  │  Controle)│  │  Aritmética) │  │
                    │  └───────────┘  └─────────────┘  │
                    │       ↕              ↕            │
                    │  ┌────────────────────────────┐   │
                    │  │  Registradores (PC, IR,    │   │
                    │  │  MAR, MBR, ACC, ...)       │   │
                    │  └────────────────────────────┘   │
                    └──────────────┬────────────────────┘
                                   │ Barramento
                    ┌──────────────┴────────────────────┐
                    │          MEMÓRIA PRINCIPAL         │
                    │  (armazena dados E instruções)     │
                    └──────────────┬────────────────────┘
                                   │ Barramento
                    ┌──────────┐       ┌───────────┐
                    │ ENTRADA  │       │  SAÍDA    │
                    │ (teclado,│       │ (monitor, │
                    │  mouse)  │       │  disco)   │
                    └──────────┘       └───────────┘

Vamos detalhar cada componente:

1. Unidade Lógica e Aritmética (ULA / ALU) — realiza operações aritméticas (adição, subtração, multiplicação) e lógicas (AND, OR, NOT, comparações). A ULA recebe operandos dos registradores, executa a operação, e deposita o resultado de volta em um registrador. Ela também gera flags de status: zero (resultado é 0), carry (houve transbordo), negative (resultado negativo), overflow (estouro aritmético).

2. Unidade de Controle (UC) — o "maestro" do processador. A UC busca a próxima instrução na memória, decodifica-a para entender qual operação deve ser feita, e gera os sinais de controle que coordenam a ULA, os registradores, a memória e os dispositivos de E/S. Ela implementa o ciclo fetch-decode-execute.

3. Memória principal — armazena tanto dados quanto instruções (essa é a essência do stored-program). Cada posição de memória tem um endereço numérico único. A memória é volátil (RAM) — quando a energia é desligada, o conteúdo se perde.

4. Dispositivos de entrada — permitem que dados externos entrem no sistema: teclado, mouse, sensor, interface de rede, disco (quando lido).

5. Dispositivos de saída — permitem que resultados saiam do sistema: monitor, impressora, interface de rede, disco (quando escrito), alto-falante.

Registradores essenciais

Dentro da CPU, existem registradores com funções específicas no ciclo de execução. Eles são a memória mais rápida do computador — acessíveis em um único ciclo de clock:

Registradores do modelo Von Neumann
Registrador    Sigla    Função
─────────────  ───────  ─────────────────────────────────────────
Program        PC       Contém o endereço da PRÓXIMA instrução
Counter                 a ser buscada na memória

Instruction    IR       Contém a instrução ATUAL que está sendo
Register                decodificada/executada

Memory         MAR      Contém o endereço de memória que será
Address Reg.            acessado (leitura ou escrita)

Memory         MBR      Contém o dado que foi lido da memória
Buffer Reg.   (MDR)     ou que será escrito na memória

Acumulador     ACC      Armazena resultados intermediários da ULA
                        (em arquiteturas simples)

Stack          SP       Aponta para o topo da pilha na memória
Pointer

Em processadores reais, há muito mais registradores — o MIPS tem 32 registradores de propósito geral, o x86-64 tem 16 registradores de 64 bits, e o ARMv8 também tem 31 registradores gerais. Mas o modelo conceitual com PC, IR, MAR e MBR é o que precisamos para entender o ciclo de execução.

O ciclo fetch-decode-execute

O coração da máquina de Von Neumann é um loop infinito com três etapas. Todo processador, do mais simples ao mais complexo, implementa alguma variação deste ciclo:

Pseudocódigo
// Simulação simplificada do ciclo Von Neumann

PC = 0x0000              // endereço da primeira instrução
executando = true

enquanto executando:

    // ─── FETCH (busca) ───────────────────────────
    MAR = PC              // coloca endereço no MAR
    MBR = memoria[MAR]    // lê instrução da memória
    IR  = MBR             // copia para o registrador de instrução
    PC  = PC + 4          // avança PC para a próxima instrução
                          // (+4 porque cada instrução tem 4 bytes)

    // ─── DECODE (decodificação) ──────────────────
    opcode    = IR[31:26]     // bits 31-26: qual operação
    rs        = IR[25:21]     // bits 25-21: registrador fonte 1
    rt        = IR[20:16]     // bits 20-16: registrador fonte 2
    rd        = IR[15:11]     // bits 15-11: registrador destino
    operacao  = decodifica(opcode)

    // ─── EXECUTE (execução) ──────────────────────
    se operacao == ADD:
        REG[rd] = REG[rs] + REG[rt]
    se operacao == LW:          // load word
        MAR = REG[rs] + offset
        MBR = memoria[MAR]
        REG[rt] = MBR
    se operacao == BEQ:         // branch if equal
        se REG[rs] == REG[rt]:
            PC = PC + (offset × 4)
    se operacao == HALT:
        executando = false

Cada passo em detalhe:

Fetch (busca): O PC contém o endereço da próxima instrução. Esse endereço é colocado no MAR e enviado à memória. A memória retorna o conteúdo daquele endereço no MBR. A instrução é copiada para o IR. O PC é incrementado (normalmente em 4 bytes, o tamanho de uma instrução em arquiteturas de 32 bits).

Decode (decodificação): A unidade de controle examina os bits da instrução no IR. Os primeiros bits (opcode) dizem qual operação realizar. Os bits restantes indicam os operandos — registradores fonte, registrador destino, valores imediatos, ou endereços de memória.

Execute (execução): A operação decodificada é realizada. Se é uma operação aritmética, a ULA calcula o resultado. Se é uma instrução de acesso à memória (load/store), a memória é lida ou escrita. Se é um branch, o PC é modificado para apontar para outro endereço.

i
Na prática, são mais etapas Processadores modernos expandem este ciclo para 5 ou mais estágios (fetch, decode, execute, memory access, write-back) para permitir pipeline — executar múltiplas instruções sobrepostas. Veremos isso em detalhe no Módulo 4.

O gargalo de Von Neumann

A arquitetura Von Neumann tem uma fraqueza estrutural: dados e instruções compartilham a mesma memória e o mesmo barramento. Isso significa que o processador não pode buscar uma instrução e ler/escrever um dado ao mesmo tempo — eles competem pelo mesmo caminho.

O gargalo
     CPU                    Memória
  ┌───────┐               ┌────────┐
  │       │ ←── busca ──→ │ instr. │
  │       │    instrução   │        │   ← mesmo barramento!
  │       │ ←── lê/escreve│ dados  │
  │       │      dado      │        │
  └───────┘               └────────┘

  Problema: a CPU é muito mais rápida que a memória.
  Ela fica ociosa esperando dados/instruções chegarem.

  Em um processador moderno a 4 GHz:
    Ciclo de clock:    ~0.25 ns
    Acesso à RAM:      ~100 ns  (400x mais lento!)

  A CPU pode executar centenas de operações no tempo
  que leva para ler um dado da memória principal.

Esse problema é chamado de Von Neumann Bottleneck (gargalo de Von Neumann), termo cunhado por John Backus em 1977. À medida que processadores ficaram mais rápidos que a memória (a "memory wall"), o gargalo se tornou o principal limitador de desempenho.

As soluções que a indústria desenvolveu para mitigar o gargalo incluem:

Arquitetura Harvard

Uma alternativa ao modelo Von Neumann é a arquitetura Harvard, que usa memórias e barramentos separados para instruções e dados:

Von Neumann vs Harvard
Von Neumann:                       Harvard:

  CPU ←→ [barramento] ←→ Memória     CPU ←→ [barram. instr.] ←→ Memória Instr.
          (único)          (única)    CPU ←→ [barram. dados]  ←→ Memória Dados
                                             (dois barramentos,  duas memórias)

Von Neumann: simples, flexível       Harvard: mais rápida (acesso simultâneo),
  (qualquer % de instruções/dados)     mas menos flexível (tamanhos fixos)

Na prática, a maioria dos processadores modernos usa uma arquitetura Harvard modificada: a cache L1 é separada em cache de instruções (I-cache) e cache de dados (D-cache) — modelo Harvard. Mas a memória principal é unificada — modelo Von Neumann. Isso combina a velocidade do acesso separado com a flexibilidade da memória unificada.

Microcontroladores usados em sistemas embarcados (como os da família AVR, usados no Arduino) frequentemente usam Harvard puro: memória Flash para instruções e SRAM para dados, com barramentos completamente separados.

No harness.os

O ciclo fetch-decode-execute é um loop de processamento — o mesmo padrão que uma session no harness.os segue: buscar contexto, decodificar a tarefa, executar a ação, gravar resultado.

Ciclo Von Neumann vs ciclo do agente
Ciclo Von Neumann               Ciclo de sessão harness.os
──────────────────────────────  ──────────────────────────────────────
FETCH:  PC → MAR → Memória      FETCH:  start_session() → carregar
        → MBR → IR                       handoff, regras, estado

DECODE: IR → opcode, operandos  DECODE: analisar o pedido do usuário,
        → sinais de controle             identificar tools necessários,
                                         consultar get_workflow()

EXECUTE: ULA calcula,           EXECUTE: chamar MCP tools, editar
         memória lê/escreve,             arquivos, rodar comandos,
         PC atualiza                     log_decision(), log_learning()

[loop]                          WRITE-BACK: end_session(summary,
                                           next_steps) → gravar handoff

O gargalo de Von Neumann           O gargalo do agente é a
é a latência da memória.           latência da context window.
Solução: cache.                    Solução: prompt cache, knowledge
                                   chunks por domínio.

O gargalo de Von Neumann — CPU rápida esperando memória lenta — tem paralelo direto com o gargalo do agente: o modelo é rápido para processar, mas carregar contexto grande é lento e caro. A solução nos dois casos é a mesma: hierarquia de cache. No harness.os, os knowledge chunks funcionam como cache L1 (dados frequentes, rápidos de carregar), enquanto o banco de dados completo é a "memória principal" (acesso mais lento, capacidade total).

Homework

  1. Desenhe o diagrama Von Neumann para um computador específico que você usa. Identifique: qual é a CPU (modelo e microarquitetura)? Quanta RAM? Quais dispositivos de entrada e saída estão conectados? Quais barramentos (USB, PCIe, NVMe)?
  2. Trace manualmente o ciclo fetch-decode-execute para as seguintes três instruções MIPS, começando com PC = 0x0000 e registradores $t0 = 5, $t1 = 3:
    Assembly MIPS
    0x0000: add  $t2, $t0, $t1    # $t2 = $t0 + $t1
    0x0004: addi $t3, $t2, 10     # $t3 = $t2 + 10
    0x0008: sw   $t3, 0($zero)    # memoria[0] = $t3
    Para cada instrução, escreva os valores de PC, MAR, MBR, IR e dos registradores relevantes em cada etapa (fetch, decode, execute).
  3. Calcule o impacto do gargalo de Von Neumann: se um processador a 4 GHz precisa acessar a RAM (100 ns de latência) a cada 10 instruções, quantos ciclos ele perde esperando a memória em 1 segundo de execução?
  4. No harness.os, identifique um exemplo de "gargalo de Von Neumann" — uma situação onde o agente fica esperando dados (contexto) chegarem em vez de estar processando. Como você resolveria esse gargalo com "caching"?

Resumo

Verifique seu entendimento

Qual é a causa principal do "gargalo de Von Neumann" (Von Neumann Bottleneck)?

  • A ULA não consegue executar operações rápido o suficiente
  • O número de registradores do processador é insuficiente
  • Os dispositivos de entrada e saída são lentos demais
  • Instruções e dados compartilham o mesmo barramento e memória, e a CPU é muito mais rápida que a memória