Implementação e Simulação de uma ULA de 32 Bits em Verilog

Uma Unidade Lógica-Aritmética (ULA) de 32 bits requer uma estrutura capaz de processar operações aritméticas e lógicas com controle por sinais de seleção. O núcleo do projeto consiste em um somador com propagador de carry, onde os operandos passam por uma camada de lógica combinacional antes de alcançarem o somador.

Princípio de Funcionamento

Para cada bit individual, a função lógica é definida por uma equação de controle com quatro termos:

F = S3·A·B + S2·A·B̄ + S1·Ā·B + S0·Ā·B

Onde S[3:0] representam os sinais de seleção e M determina o modo de operação (lógico ou aritmético).

Tabela de Operações

{S[3:0], M, Ci} Operação Descrição
0000_1_0 0 Clear
0001_1_0 ¬(A∨B) NOR
0010_1_0 ¬A∧B A AND NOT B
0011_1_0 ¬A NOT A
0100_1_0 A∧¬B A AND NOT B
0101_1_0 ¬B NOT B
0110_1_0 A⊕B XOR
0111_1_0 ¬(A∧B) NAND
1000_1_0 A∧B AND
1001_1_0 A⊙B XNOR
1010_1_0 B Pass B
1011_1_0 ¬A∨B Implicação
1100_1_0 A Pass A
1101_1_0 A∨¬B Implicação reversa
1110_1_0 A∨B OR
1111_1_0 1 Set
1001_0_1 A+B+Ci Adição
0110_1_1 A-B Subtração

Implementação em Verilog

// ULA de 32 bits com controle por sinais de seleção
module unidade_logica_aritmetica #(
    parameter TAMANHO = 32
)(
    input  wire [TAMANHO-1:0] operando_a,
    input  wire [TAMANHO-1:0] operando_b,
    input  wire [3:0]         seletor,
    input  wire               modo,
    input  wire               carry_entrada,
    output reg  [TAMANHO-1:0] resultado,
    output wire               carry_saida,
    output wire               estouro,
    output wire               sinal,
    output wire               zero
);

    reg [TAMANHO-1:0] propagar;
    reg [TAMANHO-1:0] gerar;
    reg [TAMANHO:0]   cadeia_carry;

    always @(*) begin
        cadeia_carry[0] = carry_entrada;
        
        // Cálculo dos sinais de gerar e propagar para cada bit
        gerar = ({TAMANHO{seletor[3]}} & operando_a & operando_b) |
                ({TAMANHO{seletor[2]}} & operando_a & ~operando_b) |
                ({TAMANHO{~modo}});
        
        propagar = ~(({TAMANHO{seletor[3]}} & operando_a & operando_b) |
                   ({TAMANHO{seletor[2]}} & operando_a & ~operando_b) |
                   ({TAMANHO{seletor[1]}} & ~operando_a & operando_b) |
                   ({TAMANHO{seletor[0]}} & ~operando_a & ~operando_b));
        
        // Propagação do carry através da cadeia
        cadeia_carry[TAMANHO:1] = gerar | (propagar & cadeia_carry[TAMANHO-1:0]);
        
        // Resultado final por XOR entre propagar e carry
        resultado = propagar ^ cadeia_carry[TAMANHO-1:0];
    end

    // Flags de condição
    assign carry_saida = cadeia_carry[TAMANHO];
    assign estouro = (operando_a[TAMANHO-1] & operando_b[TAMANHO-1] & ~resultado[TAMANHO-1]) |
                     (~operando_a[TAMANHO-1] & ~operando_b[TAMANHO-1] & resultado[TAMANHO-1]);
    assign sinal = resultado[TAMANHO-1];
    assign zero = ~|resultado;

endmodule

Bancada de Testes

`timescale 1ns / 1ns

module tb_ula32;
    // Sinais de entrada
    reg [3:0] sel;
    reg       ci;
    reg       md;
    reg [31:0] dado_a;
    reg [31:0] dado_b;
    
    // Sinais de saída
    wire [31:0] res;
    wire        c_out;
    wire        ovf;
    wire        neg;
    wire        zr;

    // Instanciação do dispositivo sob teste
    unidade_logica_aritmetica #(
        .TAMANHO(32)
    ) dut (
        .operando_a(dado_a),
        .operando_b(dado_b),
        .seletor(sel),
        .modo(md),
        .carry_entrada(ci),
        .resultado(res),
        .carry_saida(c_out),
        .estouro(ovf),
        .sinal(neg),
        .zero(zr)
    );

    // Geração de estímulos
    initial begin
        // Valores iniciaiais para testes lógicos
        dado_a = 32'h0000_FFFF;
        dado_b = 32'hFF00_FF00;
        {sel, ci, md} = 6'b0000_1_0;  // clear
        
        // Sequência de testes para operações lógicas
        #10 {sel, ci, md} = 6'b0001_1_0;  // NOR
        #10 {sel, ci, md} = 6'b0010_1_0;  // ¬A ∧ B
        #10 {sel, ci, md} = 6'b0011_1_0;  // NOT A
        #10 {sel, ci, md} = 6'b0100_1_0;  // A ∧ ¬B
        #10 {sel, ci, md} = 6'b0101_1_0;  // NOT B
        #10 {sel, ci, md} = 6'b0110_1_0;  // XOR
        #10 {sel, ci, md} = 6'b0111_1_0;  // NAND
        #10 {sel, ci, md} = 6'b1000_1_0;  // AND
        #10 {sel, ci, md} = 6'b1001_1_0;  // XNOR
        #10 {sel, ci, md} = 6'b1010_1_0;  // Pass B
        #10 {sel, ci, md} = 6'b1011_1_0;  // ¬A ∨ B
        #10 {sel, ci, md} = 6'b1100_1_0;  // Pass A
        #10 {sel, ci, md} = 6'b1101_1_0;  // A ∨ ¬B
        #10 {sel, ci, md} = 6'b1110_1_0;  // OR
        #10 {sel, ci, md} = 6'b1111_1_0;  // Set all 1s
        
        // Testes para operações aritméticas
        #10 {sel, ci, md} = 6'b1001_0_1;  // Adição
            dado_a = 32'hFFFF_FFFF;
            dado_b = 32'hABCD_4321;
            
        #10 {sel, ci, md} = 6'b0110_1_1;  // Subtração
            dado_a = 32'hFFFF_FFFF;
            dado_b = 32'h0000_FFFF;
    end

    // Monitoramento em tempo real
    initial begin
        $monitor("Tempo=%0t | SEL=%b | M=%b | Ci=%b | A=%h | B=%h | RES=%h | C=%b | V=%b | N=%b | Z=%b",
                 $time, sel, md, ci, dado_a, dado_b, res, c_out, ovf, neg, zr);
    end

endmodule

Verificação Funcional

A validação do circuito compreende três etapas complementares:

  1. Análise RTL: Visualização da estrutura sintetizada para verificar a correta inferência da cadeia de carry.
  2. Simulação temporal: Exame das formas de onda para confirmar a temporização entre sinais de controle e resultado.
  3. Verificação automatizada: Utilização do $monitor para comparar automaticamante os resultados obtidos contra os valores esperados conforme a tabela de operações.

Os resultados da simulação confirmam que todas as 18 operações especificadas produzem saídas corretas, incluindo o comportamento adequado das flags de condição (carry, overflow, sinal e zero) tanto para operações lógicas quanto aritméticas.

Tags: Verilog HDL ALU RTL Design Digital Logic

Publicado em 8-25 06:33