Compreender as operações aritméticas em VHDL

VHDL (VHSIC Hardware Description Language) fornece suporte robusto para implementar operações aritméticas, tornando-o uma pedra angular para projetar sistemas digitais, como processadores, processadores de sinal digital (DSPs) e unidades de controle. Blocos aritméticos como aditores, subtratores e multiplicadores são blocos de construção fundamentais, e saber implementá-los de forma eficiente é fundamental para simulação e síntese. Este artigo oferece uma exploração completa e orientada para a produção dessas operações, abrangendo seleção de tipos de dados, uso de operador, estilos arquitetônicos e técnicas comuns de otimização.

Antes de mergulhar em componentes específicos, é essencial entender os dois pacotes VHDL chave que permitem a aritmética: ieee.numeric std[ e ieee.std logic unsigned. A abordagem recomendada para novos desenhos é usar os tipos de dados numeric std[, que define []não assinado[] e ]assinado[] e fornece operadores de aritmética sobrecarregados. Este pacote garante um comportamento consistente entre simuladores e sintetizadores, evitando as falhas associadas ao pacote std logic arith mais antigo.

Todos os exemplos de código neste artigo são escritos usando IEEE 1076-2008 VHDL compatível e dispositivos Xilinx alvo ou Intel (Altera) FPGA, mas os conceitos se aplicam a qualquer fluxo de design digital.

Adições em VHDL

A adição é a operação aritmética mais fundamental. Em VHDL, você pode implementar adições em vários níveis de abstração: comportamental (usando o + operador), fluxo de dados (usando atribuição de sinal concorrente), ou estrutural (instanciando componentes de nível inferior). Para a maioria dos projetos práticos, modelagem comportamental com numeric std[ fornece o melhor equilíbrio de legibilidade e eficiência de síntese.

Adder de Ondulação Simples

Uma cadeia de adições de ondulação completa junta- se, onde a transferência de cada bit alimenta a entrega do próximo bit superior. O exemplo a seguir mostra uma aditiva de ondulação sem assinatura de 4 bits usando um processo comportamental:

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity adder4bit is
 Port (
 A : in unsigned(3 downto 0);
 B : in unsigned(3 downto 0);
 Sum : out unsigned(3 downto 0);
 Cout : out std_logic
 );
end adder4bit;

architecture Behavioral of adder4bit is
begin
 process(A, B)
 variable temp_sum : unsigned(4 downto 0);
 begin
 temp_sum := ('0' & A) + ('0' & B);
 Sum <= temp_sum(3 downto 0);
 Cout <= temp_sum(4);
 end process;
end Behavioral;

Pontos-chave:

  • A concatenação ('0' & A) estende as entradas para 5 bits, capturando a transferência.
  • Usando não assinado diretamente com o + operador é sintetizado; a ferramenta infere a lógica apropriada do adicionador (carregamento de ripple, porte-aparência, ou LUT-baseado em FPGAs).
  • A lista de sensibilidade do processo inclui todos os sinais de entrada, garantindo que a saída atualiza imediatamente em qualquer mudança (combinacional comportamento).

Adder (CLA)

Para as adegas mais amplas (por exemplo, 16 bits ou 32 bits), as adegas de ondulação introduzem um atraso significativo devido à propagação da carga. Uma adega de ondulação reduz este atraso gerando sinais de transporte em paralelo. A implementação de um CLA completo em VHDL requer a descrição do gerador ([gi = ai & b[i]ip[[i[[]]i[[[[FLT: 10]] = a[[[FLT: 11]]]i[[[[FLT: 12] XOR b[[FT: 13]i[[[[FLT: 8]]p]]i]][[[[FLT: FT: 16]]]]]

entity adder16bit is
 Port (
 A : in unsigned(15 downto 0);
 B : in unsigned(15 downto 0);
 Sum : out unsigned(15 downto 0);
 CO : out std_logic
 );
end adder16bit;

architecture Behavioral of adder16bit is
 signal temp : unsigned(16 downto 0);
begin
 temp <= ('0' & A) + ('0' & B);
 Sum <= temp(15 downto 0);
 CO <= temp(16);
end Behavioral;

Adição com Detecção de Sobrefluxo

Ao usar os números assinados, o transbordamento ocorre quando o sinal do resultado não corresponde ao sinal esperado com base nas entradas. Detectar o transbordamento é essencial no processador ALUs. O seguinte trecho mostra uma aditiva assinada com a detecção do transbordamento:

entity signed_adder is
 Port (
 A : in signed(7 downto 0);
 B : in signed(7 downto 0);
 Sum : out signed(7 downto 0);
 Overflow: out std_logic
 );
end signed_adder;

architecture Behavioral of signed_adder is
 signal extended_sum : signed(8 downto 0);
begin
 extended_sum <= (A(7) & A) + (B(7) & B); -- sign extend
 Sum <= extended_sum(7 downto 0);
 Overflow <= extended_sum(8) XOR extended_sum(7); -- sign mismatch
end Behavioral;

Subtratores em VHDL

A subtração pode ser implementada tanto pelo uso direto do operador - quanto pela adição do complemento dos dois do subtrahend ao minuend. Embora a modelagem comportamental seja simples, entender a propagação e o manuseio de resultados negativos é essencial.

Subtrator Comportamental Direto

O subtrator mais simples usa o operador - com tipos não assinados ou assinados. Para subtração não assinada, o resultado pode tornar-se negativo se B > A; nestes casos, precisamos detectar um subfluxo (emprestado). O exemplo a seguir retorna tanto a diferença quanto uma bandeira de empréstimo:

entity subtractor4bit is
 Port (
 A : in unsigned(3 downto 0);
 B : in unsigned(3 downto 0);
 Diff : out unsigned(3 downto 0);
 Borrow : out std_logic
 );
end subtractor4bit;

architecture Behavioral of subtractor4bit is
 signal temp_diff : signed(4 downto 0);
begin
 process(A, B)
 begin
 temp_diff <= signed('0' & A) - signed('0' & B);
 if temp_diff(4) = '1' then
 Borrow <= '1';
 else
 Borrow <= '0';
 end if;
 Diff <= unsigned(temp_diff(3 downto 0));
 end process;
end Behavioral;

Note a conversão para assinado para a computação intermediária; isso permite o tratamento adequado de diferenças negativas. O bit mais significativo (temp diff(4)) atua como a bandeira de empréstimo.

Subtractor usando o Complemento de Dois

Alternativamente, você pode implementar subtração adicionando o complemento dos dois de B. Esta técnica é comum ao reutilizar uma aditiva existente em uma ALU. O complemento dos dois de B é calculado como (não B) + 1. Aqui está o conceito:

signal B_comp : unsigned(3 downto 0);
signal sum_with_borrow : unsigned(4 downto 0);

B_comp <= (not B) + 1; -- two's complement
sum_with_borrow <= ('0' & A) + ('0' & B_comp);
Diff <= sum_with_borrow(3 downto 0);
Borrow <= not sum_with_borrow(4); -- borrow asserted if carry out is 0

Ambos os métodos são compatíveis com a síntese; selecione o que corresponde às preferências arquitetônicas do seu projeto.

Comparação e Subtração

Os subtratores são frequentemente usados para implementar os comparadores. Ao examinar o pedido emprestado ou o sinal da diferença, você poderá determinar se A & gt; B, A < B ou A = B sem um comparador dedicado. Por exemplo, após a subtração, se o resultado é zero (todos os bits 0), as entradas são iguais. Se o pedido emprestado/ sinal for 1, então A < B.

Multiplicadores em VHDL

A multiplicação é mais intensiva em recursos do que a adição ou subtração. VHDL suporta o operador * para tipos não assinados e assinados, o que infere um multiplicador combinado. No entanto, para larguras de bits maiores, multiplicadores combinados podem consumir lógica significativa e ter longos atrasos de propagação. Implementação sequencial e oleada são frequentemente necessários para projetos de alta velocidade.

Multiplicador combinado

Um multiplicador de 4 bits usando o operador * é trivial:

entity multiplier4bit is
 Port (
 A : in unsigned(3 downto 0);
 B : in unsigned(3 downto 0);
 Product : out unsigned(7 downto 0)
 );
end multiplier4bit;

architecture Behavioral of multiplier4bit is
begin
 Product <= A * B;
end Behavioral;

Isto indica um multiplicador combinado, que num FPGA é normalmente implementado usando fatias dedicadas de DSP (como blocos Xilinx DSP48) ou lógica baseada em LUT. Para larguras até 18 bits, a maioria das ferramentas FPGA pode mapear a multiplicação para uma única fatia DSP. Para multiplicadores mais amplos, a ferramenta de síntese pode combinar várias fatias de DSP ou usar lógica suave.

Multiplicador Sequencial (Shift- and- Add)

Para desenhos restritos à área ou quando o atraso combinado é inaceitável, pode ser usado um multiplicador sequencial que itera sobre bits. O algoritmo clássico de mudança e adição multiplica dois números de N- bits sobre ciclos de N. Abaixo está um exemplo simplificado de multiplicador de 4 bits, sem assinatura, com sinais de controle omitidos para clareza):

entity sequential_multiplier is
 Port (
 clk : in std_logic;
 reset : in std_logic;
 start : in std_logic;
 A : in unsigned(3 downto 0);
 B : in unsigned(3 downto 0);
 done : out std_logic;
 Product : out unsigned(7 downto 0)
 );
end sequential_multiplier;

architecture Behavioral of sequential_multiplier is
 signal multiplicand : unsigned(7 downto 0);
 signal multiplier : unsigned(3 downto 0);
 signal product_reg : unsigned(7 downto 0);
 signal count : integer range 0 to 4;
 signal busy : std_logic;
begin
 process(clk)
 begin
 if rising_edge(clk) then
 if reset = '1' then
 count <= 0;
 busy <= '0';
 product_reg <= (others => '0');
 done <= '0';
 elsif start = '1' and busy = '0' then
 multiplicand <= "0000" & A; -- left-aligned 4-bit multiplicand
 multiplier <= B;
 product_reg <= (others => '0');
 count <= 0;
 busy <= '1';
 done <= '0';
 elsif busy = '1' then
 if multiplier(0) = '1' then
 product_reg <= product_reg + multiplicand;
 end if;
 multiplicand <= multiplicand(6 downto 0) & '0'; -- shift left
 multiplier <= '0' & multiplier(3 downto 1); -- shift right
 count <= count + 1;
 if count = 3 then
 busy <= '0';
 done <= '1';
 end if;
 end if;
 end if;
 end process;
 Product <= product_reg;
end Behavioral;

Este design usa uma adição de L-bit por ciclo de relógio (4 ciclos para entradas de 4 bits). Ele economiza área, mas sacrifica a taxa de transferência e latência.

Multiplicador Pipelined

Para aplicações de alto rendimento, um multiplicador encriptado registra entre estágios da multiplicação combinacional. Muitas ferramentas de síntese FPGA podem automaticamente canalizar um multiplicador quando você adiciona registros de tubulação. Por exemplo, usando um para ... gerar ] loop ou inserção manual de estágio:

-- Pipelined unsigned 4x4 multiplier (2-stage pipeline)
architecture Pipelined of multiplier4bit is
 signal stage1_prod : unsigned(7 downto 0);
 signal stage1_A, stage1_B : unsigned(3 downto 0);
 signal stage2_prod : unsigned(7 downto 0);
begin
 process(clk)
 begin
 if rising_edge(clk) then
 stage1_A <= A;
 stage1_B <= B;
 stage1_prod <= stage1_A * stage1_B; -- first stage
 stage2_prod <= stage1_prod; -- second stage
 Product <= stage2_prod;
 end if;
 end process;
end Pipelined;

Esta simples abordagem em dois estágios duplica a taxa de rendimento (um resultado por relógio após a latência inicial) enquanto adiciona apenas uma camada extra de registo. Mais etapas podem ser adicionadas para frequências de relógio mais elevadas.

Usando cortes DSP

Os FPGAs modernos contêm fatias de DSP endurecidas configuradas para multiplicação e acumulação. Em VHDL, usando o operador *[[[FLT: 1]] frequentemente infere automaticamente estes blocos. Para garantir a inferência de DSP, siga as orientações do fornecedor: mantenha os operandos dentro da largura do corte (por exemplo, 18x18, 18x25), evite grandes operadores relacionais no resultado e use a largura apropriada. Para a série Xilinx 7, você também pode instanciar o primitivo ]DSP48E1[[FLT: 3]], mas usar o operador é preferido para portabilidade.

Guia de síntese do Xilinx Vivado

Técnicas de otimização e Considerações de Síntese

Ao implementar operações aritméticas em VHDL, diversos fatores afetam a qualidade dos resultados:

  • [[FLT: 0]] Largura de dados: Use a menor largura necessária para reduzir a lógica. Por exemplo, se as entradas são de 5 bits, use [[FLT: 2]] não assinado(4 para baixo para 0) [[FLT: 3]].
  • [[FLT: 0]] Atributos da Síntese: [[FLT: 1]] Aplicar atributos como [[FLT: 2]] manter [[FLT: 3]], [[FLT: 4] usar dsp, ou [[FLT: 6]] mult style[[[FLT: 7]]] influenciar o mapeamento. Por exemplo: ]
  • Resource Sharing: Se várias operações usarem o mesmo adicionador ou multiplicador, considere reutilizar hardware através de um componente compartilhado ou um único bloco aritmético com entradas multiplexadas.
  • Pipelining: Inserir registros para atender às restrições de tempo. Para cadeias aditivas, balancear a colocação do registro para evitar caminhos combinatórios longos.
  • Assinado vs Sem Assinatura: Uso assinado para operações assinadas; a ferramenta de síntese tratará o MSB como um bit de sinal, afetando a lógica aritmética inferida.
  • Carry Chains:Para grandes adições, a cadeia de transporte é um recurso dedicado em FPGAs. Certifique-se de que sua ferramenta de síntese não está quebrando a cadeia usando sinais intermediários inapropriados.

Combinando operações: Exemplo ALU

Para ilustrar como as aditivas, subtratores e multiplicadores se integram em um projeto maior, considere uma unidade lógica aritmética simples (UAL) que pode adicionar, subtrair ou multiplicar dois valores de 8 bits com base em um sinal selecionado:

entity alu is
 Port (
 A, B : in signed(7 downto 0);
 op : in std_logic_vector(1 downto 0); -- "00": add, "01": sub, "10": mul
 result : out signed(15 downto 0)
 );
end alu;

architecture Behavioral of alu is
begin
 process(A, B, op)
 begin
 case op is
 when "00" => result <= resize(A + B, 16); -- sign extend
 when "01" => result <= resize(A - B, 16);
 when "10" => result <= A * B;
 when others => result <= (others => '0');
 end case;
 end process;
end Behavioral;

Este ALU reutiliza o mesmo registo de resultados e combina as três operações. Em síntese, cada operação é implementada como um bloco separado, com a saída seleccionada por um multiplexer. Dependendo do dispositivo alvo, o multiplicador pode ser o caminho crítico.

Usando núcleos IP para aritmética complexa

Para operações avançadas (por exemplo, ponto flutuante, raiz quadrada, módulo), ou quando for necessário o máximo desempenho, é aconselhável usar núcleos IP fornecidos pelo fornecedor. Estes são altamente otimizados e têm modelos de simulação verificados. Em VHDL, você instancia um núcleo IP como componente, mapeando seus sinais para suas portas. Os núcleos comuns incluem:

  • Explosivo de ponto flutuante para adicionar/sub/multiplicar/dividir no formato IEEE 754.
  • Altera (Intel) ALTMULT ADD] para operações multiples.
  • Divider de Latência] para divisão de ponto fixo.

Usando núcleos IP minimiza o risco e muitas vezes resulta em melhor desempenho do que equivalentes codificados manualmente. Consulte a documentação do fornecedor para modelos de instanciação.

[[FLT: 0]] Guia de núcleos IP Intel FPGA

Ensaios e verificação

A simulação é crítica para os desenhos aritméticas. Escreva os benchês de teste que exercitam casos de canto: overflow, zero, valores máximos e sinais mistos (para tipos assinados). Para multiplicadores, teste todas as combinações das menores entradas para verificar o algoritmo. Use a instrução [[FLT: 0]] assert[[[ FLT: 1]]] para verificar os resultados esperados. Exemplo de trecho de bancada de teste para um adder de 4 bits:

signal A, B : unsigned(3 downto 0);
signal Sum : unsigned(3 downto 0);
signal Cout : std_logic;
...
A <= "1100"; B <= "0011"; wait for 10 ns;
assert (Sum = "1111" and Cout = '0')
 report "Adder failed for 12 + 3" severity error;

Para desenhos maiores, considere usar estímulos aleatórios e modelos dourados em linguagens de scripting (Python, Tcl) para gerar vetores de teste.

Técnicas de bancada de ensaio VHDL (SynthWorks)

Conclusão

A implementação de operações aritméticas em VHDL é uma combinação de compreensão aritmética digital, uso proficiente de tipos de dados e operadores, e consciência do comportamento da ferramenta de síntese. Os aditivos e subtratores são simples ao usar numeric std, enquanto os multiplicadores requerem uma cuidadosa consideração do desempenho e da área. Ao empregar descrições comportamentais, você rapidamente consegue projetos de trabalho, e aplicando técnicas como pipelining, compartilhamento de recursos e inferência de DSP, você otimiza para hardware do mundo real. Com os exemplos e diretrizes fornecidos, você está equipado para construir unidades aritméticas robustas para qualquer sistema digital.

Para leitura posterior, consulte o Manual de Referência da Língua IEEE VHDL e documentação específica do fornecedor sobre inferência aritmética.

IEEE Std 1076-2008 Manual de Referência da Língua VHDL