Material de Estudos PSEL Coretech 2025

Material de estudos ofertado para a dinâmica em grupo da liga acadêmica Coretech em 2025.

Os Fundamentos da Lógica Digital

Abstração Digital

Da Física Contínua à Lógica Discreta

Sinais Analógicos

  • Confiabilidade quase perfeita.
  • Funções contínuas do tempo.
  • Capturam a natureza do mundo físico com fidelidade.
  • Suscetíveis a interferência externa.

Sinais Digitais

  • Precisão infinita.
  • Construídos com base em abstração.
  • Operam com um conjunto finito de valores discretos.
    • O mais famoso sendo o binário.
    • Fornece imunidade a ruído (basicamente, dados irrelevantes são fácilmente bloqueados).
  • Sistemas robustos e reproduzíveis.

Conversores

São eles que conectam sistemas analógicos e digitais. Um Conversor Analógico-Digital (ADC) converte um sinal analógico em formato digital mediante dois processos:

  • Discretização: Amostra o sinal em intervalos de tempo discretos.
  • Quantização: Mapeia a amplitude contínua de cada amostra para um valor digital finito..

Um Conversor Digital-Analógico (DAC) faz o inverso.

Sistemas Binários e Codificação da Informação

Bit

  • Unidade do sistema binário.
  • 0 ou 1.
  • Usado principalmente para a representação de números.

BCD (Binary-Coded Decimal)

Representa cada dígito decimal (0-9) com um grupo de quatro bits.

ASCII (American Standard Code for Information Interchange)

Código de 8 bits que mapeia números, letras e símbolos para valores binários, permitindo o processamento e armazenamento de texto ("ultrapassado" pelo Unicode).

Código Gray

Sistema de codificação binária onde dois valores sucessivos diferem em apenas um bit. Base para a organização do Mapa de Karnaugh, uma ferramenta de simplificação lógica.

Sistemas de Numeração e Conversão de Bases

A base de todos os sistemas de numeração modernos é a notação posicional, onde o valor de um dígito é determinado tanto pelo seu valor intrínseco quanto pela sua posição dentro do número, ponderado por uma potência de base (radix).

De Decimal para Outras Bases

Para converter a parte inteira de um número decimal para uma base \( r \), utiliza-se o método das divisões sucessivas. O número é repetidamente dividido pela nova base \( r \), e os restos são coletados. o primeiro resto é o digito menos significativo (LSB) e o último quociente é o digito mais significativo (MSB).

Exemplo: Converta o número 100 para binário.

\[ 100 \div 2 = 50 (0) \to 50 \div 2 = 25 (0) \to 25 \div 2 = 12 (1) \to 12 \div 2 = 6 (0) \to 6 \div 2 = 3 (0) \to 3 \div 2 = 1 (1) \to 1 \div 2 = 0 (1) \]

Portanto, 100 em binário é 1100100.

De Outras Bases para Decimal

Utiliza-se a expansão polinomial. Cada dígito do número é multiplicado pela base \( r \) elevada à potência de sua posição (começando em 0 para o dígito mais à direita) e os resultados são somados.

Exemplo: Converta o número binário 11012 para decimal.

\[ 1 \cdot 23 + 1 \cdot 22 + 0 \cdot 21 + 1 \cdot 20 = 8 + 4 + 0 + 1 = 1310 \]

Atalhos entre Binário, Octal e Hexadecimal

  • Binário → Octal: Os bits são agrupados em conjuntos de três, da direita para a esquerda (se tiver menos que três, o número é preenchido à esquerda por zeros). Cada número é então substituído pelo seu octal equivalente. Exemplo: Converta o número binário 1100100 para octal.

Agrupando da direita para a esquerda:

\[ 1 100 100 \]

Como o grupo à esquerda possui apenas um dígito, temos que preenchê-lo:

\[ 001 100 100 \]

E agora podemos fazer a conversão de verdade:

\[ 001 = 1 100 = 4 100 = 4 \]

Aqui está uma tabela de conversão entre eles:

BinárioOctal
0000
0011
0102
0113
1004
1015
1106
1117
  • Binário → Hexadecimal: Os bits são agrupados em conjuntos de quatro (se tiver menos que quatro, o número é preenchido à esquerda por zeros). Para descobrir o valor de um grupo, cada número dele é multiplicado por 2 elevado ao seu índice (começando por 0) da direita para a esquerda—depois, é feito a soma dos resultados. Isso é feito com todos os grupos, da direita para a esquerda. Daí a gente junta os resultados (\( 2 \circ 2 = 22 \)).

Exemplo: Converta o número binário 1100100 para hexadecimal.

\[ 0100 110 \to 0100 0110 \]

Pro primeiro grupo:

\[ 0 \cdot 2^{3} + 1 \cdot 2^{2} + 1 \cdot 2^{1} + 0 \cdot 2^{0} = 0 + 4 + 2 + 0 = 6 \]

Pro segundo:

\[ 0 \cdot 2^{3} + 1 \cdot 2^{2} + 0 \cdot 2^{1} + 0 \cdot 2^{0} = 4 \]

E daí a gente junta eles:

\[ 6 \circ 4 = 64 \]

Note que convertemos o número binário para decimal primeiro, e daí o convertemos para hexadecimal.

Aqui está uma tabela de conversão entre eles:

DecimalHexadecimal
00
11
22
33
44
55
66
77
88
99
10A
11B
12C
13D
14E
15F

A Arítmética Binária em Hardware

Números com sinal ("sinal" se referindo a positivo ou negativo) são representados por padrão utilizando o sistema de complemento de dois.

Complemento de Dois

O complemento de dois de um número binário é calculado em um processo de dois passos: primeiro, todos os bits são invertidos (complemento de um), e em seguida, 1 é somado ao resultado. Neste sistema, o bit mais significativo (MSB) atua como indicador de sinal: 0 para números positivos e 1 para números negativos.

A Linguagem e a Otimização de Circuitos

A Linguagem dos Circuitos: Álgebra Booleana

Esse campo da matemática descreve as relações entre variaveis lógicas.

Fundamentos Axiomáticos

  • Conjunto de elementos (todos os valores do campo): \( {0, 1} \).
  • Operadores básicos: AND (produto lógico), OR (soma lógica) e NOT (complemento lógico).

Postulados Fundamentais

  • Fechamento: O resultado de uma operação com elementos do conjunto também pertence ao conjunto.
  • Elementos de Identidade: Existe um elemento de identidade para cada operação: 0 para a operação OR (\( (A + 0 = A) \)) e 1 para a operação AND (\( A \cdot 1 = A \)).
  • Comutatividade: \( A + B = B + A; A \cdot B = B \cdot A \).
  • Distributividade: \( A \cdot (B + C) = A \cdot B + A \cdot C; A + (B \cdot C) = (A + B) \cdot (A + C) \).

Teoremas

  • Idempotência: \( A + A = A \).
  • Absorção: \( A + A \cdot B = A \).
  • Associatividade: \( (A + B) + C = A + (B + C) \).

Princípio da Dualidade

Qualquer teorema booleano válido permanece válido se os operadores AND e OR forem trocados entre si, e os elementos de identidade 0 e 1 também forem trocados.

Teoremas de De Morgan

Fornecem um método formal para negar expressões complexas e estabelecem a base matemática para a equivalência entre diferentes formas de formas lógicas.

  1. O complemento de uma soma é o produto dos complementos: \( (A + B)' = A' \cdot B' \)
  2. O complemento de um produto é a soma dos complementos: \( (A \cdot B)' = A' + B' \)

Formas Canônicas

Existem algumas formas de representar uma função booleana.

Tabela-Verdade

A representação mais fundamental. Lista os resultados de cada combinação possível.

Exemplo:

PQP AND Q
VVV
VFF
FVF
FFF

Mintermos e Maxtermos

Um mintermo é um termo produto (AND) que inclui todas as variáveis de entrada e que resulta em 1, Um maxtermo é um termo soma (OR) que resulta em 0 para uma única combinação de entrada.

Soma de Produtos (SOP) e Produto de Soma(POS)

Qualquer função lógica pode ser expressa de duas formas canônicas: como uma soma lógica (OR) de todos os mintermos para os quais a função é 1 (forma SOP), ou como um produto lógico (AND) de todos os maxtermos para os quais a função 0 (forma POS). É basicamente os conceitos de Forma Normal Disjuntiva e Forma Normal Conjuntiva de Fundamentos.

Portas Lógicas como Implementações Físicas

A implementação física da álgebra booleana tem como base as portas lógicas, os circuitos eletrônicos que executam as operações lógicas fundamentais.

  • Portas Básicas: AND, OR e NOT (inversor).
  • Portas Universais: NAND (NOT-AND) e NOR (NOT-OR). Tem esse nome pois qualquer função booleana pode ser construída usando exclusivamente elas.

Esses circuitos são implementados com transistores.

A tabela a seguir consolida os postulados e teoremas fundamentais da Álgebra Booleana.

NomeForma AND (Produto)Forma OR (Soma)
Postulados
Identidade\( A \cdot 1 = A \)\( A + 0 = A \)
Nulidade\( A \cdot 0 = 0 \)\( A + 1 = 1 \)
Comutatividade\( A \cdot B = B \cdot A \)\( A + B = B + A \)
Associatividade\( (A \cdot B) \cdot C = A \cdot (B \cdot C) \)\( (A + B) + C = A + (B + C) \)
Distributividade\( A (B + C) = (A \cdot B) + (A \cdot C) \)\( A + (B \cdot C) = (A + B) \cdot (A + C) \)
Teoremas
Idempotência\( A \cdot A = A \)\( A + A = A \)
Involução\( (A')' = A \)
Complemento\( A \cdot A' = 0 \)\( A + A' = 1 \)
Absorção\( A \cdot (A + B) = A \)\( A + (A \cdot B) = A \)
De Morgan\( (A \cdot B)' = A' + B' \)\( (A + B)' = A' \cdot B' \)

Princípios de Minimização Lógica

Passo essencial para otimização e economia.

A Lógica da Simplificação

Implementando uma função booleana na base de SOP ou POS gera um circuito que funciona, mas muito extenso. A simplificação busca encontrar uma expressão equivalente que utilize menos recursos.

Minimização Gráfica: O Mapa de Karnaugh (K-Map)

Usado para funções com até quatro ou cinco variáveis.

Estrutura

É uma tabela-verdade reorganizada em uma grade bidimensional. A numeração das linhas e colunas segue o Código Gray, onde células adjacentes (incluindo as bordas opostas, como se o mapa estivesse enrolado) diferem em apenas um bit. Essa adjacência é a chave para a simplificação.

Agrupamento

O processo de simplificação envolve agrupar células adjacentes que contem 1s. Os grupos devem ser retangulares e conter um número de células que seja uma potência de dois (1, 2, 4, ...). O objetivo é formar os maiores grupos possíveis, cobrindo todos os 1s.

Eliminação de Variáveis

Cada grupo corresponde a um único termo produto na expressão simplificada. As variáveis que mudam de estados (aparecem com 0 e 1) em um grupo são eliminadas. Por exemplo, um grupo de duas células elimina uma variável, um grupo de quatro elimina duas, e assim por diante.

Condições "Don't Care"

Em muitos projetos, certas combinações de entrada nunca ocorrem, ou a saída é irrelevante. Podemos incluí-las em grupos de 1 para formar grupos maiores, mas é ideal cortá-las.

Blocos de Construção de Sistemas Digitais

Circuitos Lógicos Combinacionais

Circuitos combinacionais são definidos pela propriedade de que suas saídas são função exclusivamente de suas entradas atuais. Eles não possuem memória de eventos passados; para um dado conjunto de entradas, a saída será sempre a mesma.

Circuitos Aritméticos

Meio Somador (Half-Adder)

Circuito mais simples para adição, somando dois bits de entrada (\( A \) e \( B \)) para produzir uma Soma \( S \) e um "Vai-um" de saída Cout. As equações lógicas são \( S = A + B \) (XOR) e Cout = \( A \cdot B \) (AND).

Somador Completo (Full-Adder)

Adiciona três bits: \( A \), \( B \) e um "Vai-um" de entrada Cin (standard input). Permite somar números de múltiplos bits. Pode ser construído a partir de dois meio somadores e uma porta OR.

Somador de Propagação (Ripple-Carry Adder)

Para somar números de \( n \) bits, \( n \) somadores completos são conectados em cascata. O Cout (standard output) de um estágio se torna o Cin do próximo, da direita para a esquerda. Note que esse tipo de somador possui uma limitação de desempenho.

Circuitos Subtratores

Feito a partir da conversão de um somador de propagação. Para calcular \( A - B \), o operando \( B \) é invertido (formando seu complemento de um) e o Cin do primeiro somador completo é ajustado para 1. A soma resultante é \( A + B' + 1 \), que é a definição de substração usando complemento de dois.

Circuitos de Roteamento e Seleção de Dados

Multiplexador (MUX)

Atua como uma chave digital. Possui \( 2n \) entradas de dados \( n \), \( n \) linhas de seleção e uma única saída. O valor binário nas linhas de seleção determina qual das entradas de dados é conectada à saída. É um componente fundamental para selecionar entre diferentes fontes de dados, como em barramentos de dados de um processador.

Decodificador

Converte um código de entrada binário de \( n \) bits em \( 2n \) saídas únicas. Para qualquer código de entrada, apenas uma das linhas de saída é ativada. Serve para decodificar endereços de memória e instruções de CPU.

Circuitos de Codificação

Codificador (Encoder)

Realiza a operação inversa de um decodificador. Possui \( 2n \) linhas de entrada e produz um código de saída binário de \( n \) bits correspondente à única linha de entrada que está ativa.

Codificador de Prioridade

Determina os processos mais críticos aos quais o processador deveria focar.

Circuitos Lógicos Sequenciais

Introduzem o conceito de estado, onde a saída depende não apenas das entradas atuais, mas também da sequência de entradas passadas.

Latches vs. Flip-Flops: Uma Distinção Crítica

Latches (Sensíveis ao Nível)

Elementos de memória "transparentes". a saída segue sua entrada de dados enquanto o sinal de habilitação (ou clock) está em um nível ativo.

Flip-Flops (Disparados por Borda)

Blocos de construção dos sistemas síncronos. Muda seu estado armazenado apenas em uma transição específica do sinal do clock (a borda de subida, posedge, ou a borda de descida, negedge). Tem alguns tipos:

  • Flip-Flop D: O mais utilizado para armazenamento de dados.
  • Flip-Flop JK: Uma versão mais versátil. Pode manter o estado, setar, resetar ou alternar (toggle) seu estado.
  • Flip-Flop T: Uma versão simplificada do \( JK \). Alterna seu estado a cada pulso de clock.

De um lado, os latches são mais simples—por outro, os flip-flops funcionam melhor com projetos mais complexos.

Máquinas de Estados Finitos (FSMs)

Modelo matemático e conceitual para o projeto de qualquer circuito sequencial. Existem dois modelos principais:

  • Máquina de Moore: As saídas são função apenas do estado atual do sistema. Mais complexas.
  • Máquina de Mealy: As saídas são função tanto do estado atual quanto das entradas atuais. Mais compactas, porém introduzem desafios de temporização em sistemas mais complexos.

De Lógica a Sistemas: Arquiteturas Embarcadas e Verilog HDL

A Arquitetura de Sistemas Embarcados

Definindo o Paradigma Embarcado

Um sistema embarcado é um sistema especializado, projetado para executar uma função dedicada como parte de um dispositivo maior.

Características

  • Dedicados a uma tarefa específica
  • Operam sob restrições de tempo real
  • Otimizados para métricas como custo, consumo de energia e tamanho físico

O Núcleo de Processamento: MCU vs. MPU

  • Microcontrolador (MCU): Integra a CPU, memória e uma variedade de periféricos em um único circuito integrado. Minimiza o tamanho, o custo e o consumo de energia do sistema, pois todas as conexões são internas. Entretanto, a quantidade de memória e o conjunto de periféricos são fixos no momento de fabricação.
  • Microprocessador (MPU): Consiste principalmente na CPU. Memória e periféricos são componentes externos conectados através de um barramento, permitindo que um projetista escolha a quantidade exata de RAM, o tipo de armazenamento e os periféricos específicos necessários para a aplicação. Entretanto, isso resulta num sistema, maior, mais caro e com maior consumo de energia.

Memória e Periféricos

Hierarquia de Memória

  • Memória não volátil (Flash, EEPROM): Guarda o que deveria ser permanente, como o programa a ser executado.
  • Memória volátil (SRAM): Guarda o resto.

Periféricos Comuns

  • GPIO (General-Purpose Input/Output): Pinos digitais para ler o estado de sensores (como botões) e controlar atuadores (como LEDs).
  • Temporizadores/Contadores: Essenciais para gerar atrasos precisos, criar sinais de Modulação por Largura de Pulso (PWM) para controlar motores e medir a frequência de eventos externos.

Interfaces de Comunicação

Protocolos padronizados como UART (comunicação serial assíncrona simples), SPI e I2C (protocolos seriais síncronos para comunicação com outros chips na mesma placa).

Descrição de Hardware com Verilog HDL

A Mudança de Paradigma para HDLs

Em vez de desenho manual, usamos Linguagens de Descrição de Hardware (HDLs). Verilog e VHDL são as duas linguagens dominantes na indústria.

Fundamentos de Verilog

Módulo

Um module define um componente de hardware com uma lista de portas (entradas e saídas) e uma descrição de sua lógica interna.

Tipos de dados

  • wire: Representa uma conexão física, como um fio. Não pode armazenar um valor; deve ser continuamente dirigido por algo, como a saída de uma porta lógica ou uma atribuição contínua (assign). Usado para modelar conexões em lógica combinacional.
  • reg: Representa um elemento de armazenamento de dados. Retém seu valor até que um novo seja atribuido a ele em um bloco procedural (always ou initial).

Níveis de Abstração

  • Estrutural (Nível de Porta): O nível mais baixo de abstração. Análoga a um diagrama esquemático.
  • Fluxo de Dados (Dataflow): Descreve como os dados fluem através do circuito usando atribuições contínuas (assign) e operadores lógicos. Ideal para descrever uma lógica combinacional com base em suas equações booleanas.
  • Comportamental (Behavioral): O nível mais alto de abstração. A funcionalidade do circuito é descrita algoritmicamente usando blocos procedurais como always e initial, e construções de linguagem de alto nível como if-else, case e laços (loops). Usado para descrever tanto lógica combinacional complexa quanto toda a lógica sequencial (FSMs, contadores, registradores).

O Fluxo de Projeto Digital

  1. Projeto RTL (Register-Transfer Level): O projetista escreve o código Verilog em nível de abstração conhecido como RTL. Este é um estilo de codificação, principalmente comportamental e de fluxo de dados, que descreve como os dados são transferidos entre registradores e processados pela lógica combinacional. É o nível de entrada para as ferramentas de síntese.
  2. Simulação Funcional (Verificação): Antes de criar o hardware físico, o projeto deve ser verificado. Um testbench — módulo Verilog separado que não é sintetizável — é escrito para instanciar o projeto (chamado de Device Under Test, ou DUT), gerar sinais de estímulo para suas entradas e verificar se as saídas correspondem ao comportamento esperado. Garante a correção lógica do projeto.
  3. Síntese Lógica: Uma ferramenta de síntese de software converte o código Verilog RTL em uma netlist de nível de porta, que é uma descrição estrutural baseada em uma biblioteca de células lógicas específicas de uma tecnologia.
  4. Verificação de Temporização: Após a síntese, a netlist é analisada para verificar se o circuito atenderá aos requisitos de temporização e para determinar a frequência máxima de operação.

Aplicação Prática

O Microcontrolador ATmega328P

É o cérebro do Arduino Uno. Serve como exemplo de um MCU.

Arquitetura e Núcleo

  • CPU: O ATmega328P é construído em torno de um núcleo RISC (Reduced Instruction Set Computer) AVR de 8 bits.
  • Arquitetura de Memória: Utiliza uma arquitetura Harvard modificada, que possui espaços de endereço e barramentos separados para a Memória de Programa (Flash) ea Memória de Dados (SRAM).

Mapa de Memória e Registradores

  • Memória de Programa (Flash): Possui 32 KB de memória Flash não volátil para armazenar o código do usuário. Uma seção desta memória pode ser dedicada a um Bootloader, eliminando a necessidade de um programador de hardware externo.
  • Memória de Dados (SRAM): Possui 2 KB de memória SRAM volátil. Seu mapa de memória é dividido em três seções principais:
    1. Registradores de Propósito Geral (R0-R31): Os primeiros 32 bytes são mapeados para os 32 registradores de 8 bits da CPU. São os registradores de trabalho mais rápidos, diretamente conectados à ALU.
    2. Registradores de I/O (SFRs): Os próximos 64 bytes (mais uma seção estendida) são mapeados para os Registradores de Função Especial (SFRs), que controlam todos os periféricos do chip.
    3. SRAM Interna: O restante do espaço é usado para variáveis da aplicação e para a pilha de execução.
  • EEPROM: Possui 1 KB de memória de dados não volátil, ideal para armazenar dados de configuração ou calibração que precisam persistir mesmo quando a energia é desligada.

Interface com Periféricos: Os Registradores de I/O

A arquitetura de I/O mapeada em memória é o mecanismo fundamental que permite que software de alto nível controle hardware físico.

Para controlar o pinos de I/O de propósito geral (GPIO), três registradores são essenciais para cada porta:

  • DDRD (Data Direction Register D): Configura a direção de cada pino da porta D. Escrever 1 em um bit o torna uma saída; escrever 0 o torna uma entrada.
  • PORTD (Port D Data Register): Se um pino é configurado como saída, escrever 1 em seu bit correspondente no PORTD o leva a um nível de tensão alto, e 0 a um nível baixo. Se o pino é uma entrada, escrever 1 ativa um resistor de pull-up interno, o que é útil para conectar botões.
  • PND (Port D Input Pins Register): A leitura deste registrador retorna o estado lógico atual dos pinos físicos da Porta D, independentemente de estarem configurados como entrada ou saída.

O Fluxo de Trabalho Moderno de Projeto e Verificação

Simulação com Testbenches Verilog

A arquitetura típica de um testbench inclui:

  1. Instanciação do DUT: O módulo a ser testado é instanciado dentro do testbench.
  2. Geração de Estímulos: Blocos initial e always são usados para gerar sianis de entrada para o DUT, como o clock, o reset e os dados de teste.
  3. Verificação de Saídas: O testbench monitora as saídas do DUT e as compara com os valores esperados.

Ferramentas como ModelSim são o padrão da indústria para simulação, enquanto plataformas online como EDA Playground oferecem simuladores para aprendizado e prototipagem.

Módulos Verilog de Exemplo

Contador de 4 bits

Implementa um contador síncrono de 4 bits com um reset síncrono. Na borda de subida do clock, se o reset estiver ativo, o contador vai para zero; caso contrário, ele incrementa.

module counter_4bit (
    output reg [3:0] count,
    input wire clk,
    input wire reset
);

    always @(posedge clk or posedge reset) begin
        if (reset)
            count <= 4'b0000;

        else
            count <= count + 1;
    end
endmodule

Detector de Sequência "1011" (Moore FSM)

Traduz o diagrama de estados de uma FSM de Moore para Verilog. Usa um registrador para o estado atual (current_state) e lógica combinacional para determinar o próximo estado (next_state) com base no estado atual e na entrada. A saída depende apenas do estado atual.

module sequence_detector_moore (
    output reg detector_out,
    input wire sequence_in,
    input wire clk,
    input wire reset
);
    parameter S_IDLE = 3'b000, S_1 = 3'b001, S_10 = 3'b010, S_101 = 3'b011, S_1011 = 3'b100;
    reg [2:0] current_state, next_state;

    // Lógica sequencial para atualização do estado
    always @(posedge clk or posedge reset) begin
        if (reset)
            current_state <= S_IDLE;
        else
            current_state <= next_state;
    end

    // Lógica combinacional para transição de estado
    always @(current_state or sequence_in) begin
        case (current_state)
            S_IDLE: next_state = sequence_in? S_1 : S_IDLE;
            S_1: next_state = sequence_in? S_1 : S_10;
            S_10: next_state = sequence_in? S_101 : S_IDLE;
            S_101: next_state = sequence_in? S_1011 : S_10;
            S_1011: next_state = sequence_in? S_1 : S_IDLE;
            default: next_state = S_IDLE;
        endcase // case (current_state)
    end
    // Lógica de saída (depende apenas do estado)
    always @(current_state) begin
        if (current_state == S_1011)
            detector_out = 1'b1;
        else
            detector_out = 1'b0;
    end
endmodule

ALU Simples de 8 bits

Descreve um ALU comportamental que realiza uma de oito operações (adição, subtração, AND, OR, etc.) em duas entradas de 8 bits, com base em um código de operação (0p) de 3 bits.

module simple_alu (
    output reg [7:0] R,
    input wire [7:0] A, B,
    input wire [2:0] Op);
    always @(*) begin
        case (Op)
            3'b000: R = A + B;    // Adição
            3'b001: R = A - B;    // Subtração
            3'b010: R = A & B;    // AND
            3'b011: R = A | B;    // OR
            3'b100: R = A ^ B;    // XOR
            3'b101: R = ~A;       // NOT
            3'b110: R = ~(A & B); // NAND
            3'b111: R = ~(A | B); // NOR
            default: R = 8'h00;
        endcase // case (Op)
endmodule