Comprendere le operazioni aritmetiche in VHDL

VHDL (VHSIC Hardware Description Language) fornisce un supporto robusto per l'implementazione di operazioni aritmetiche, rendendolo un punto di riferimento per la progettazione di sistemi digitali come processori, processori di segnale digitale (DSP), e unità di controllo. Blocchi aritmetici come adder, subtrattori e moltiplicatori sono blocchi di costruzione fondamentali, e sapere come implementarli in modo efficiente è fondamentale per l'utilizzo sia per la simulazione e la sintesi.

Prima di immergersi in componenti specifici, è essenziale capire i due pacchetti VHDL chiave che permettono aritmetici: iee.numeric std] e eee.std logic unsigned]. L'approccio raccomandato per nuovi disegni è quello di utilizzare [FLT 4]

Tutti gli esempi di codice in questo articolo sono scritti utilizzando [IEEE 1076-2008[[] VHDL compatibile e i dispositivi di Xilinx o Intel (Altera) FPGA, ma i concetti si applicano a qualsiasi flusso di progettazione digitale.

Adders in VHDL

In VHDL, è possibile implementare adder a vari livelli di astrazione: comportamentale (utilizzando ]+[] operatore), flusso di dati (utilizzando l'assegnazione del segnale concomitante), o strutturale (constante componenti di basso livello di efficienza). Per la maggior parte progetti pratici, modellazione comportamentale con sintesi numerica std[

Semplice Aggiunta di Ondulazione

Una catena di scale a timone increspata insieme, dove il trasporto di ogni bit alimenta il riporto del prossimo bit più alto. L'esempio seguente mostra una scala a 4 bit senza segno increspata usando un processo comportamentale:

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;

Punti chiave:

  • La concatenazione ('0' & A) estende gli ingressi a 5 bit, catturando il passaggio.
  • Utilizzando unsigned[] direttamente con l'operatore []+[]] è sintetizzabile; lo strumento inferisce la logica appropriata della scala (ripple-carry, port-lookahead, o LUT-based in FPGAs).
  • L'elenco della sensibilità del processo include tutti i segnali di input, garantendo gli aggiornamenti di uscita immediatamente su qualsiasi cambiamento (comportamento internazionale).

Aggiunta di carry-Lookahead (CLA)

[LT] [[[L]]] [[L]]] [[L]]] [[L]]] [[L]]] [[L]]] [[L]]] [[L'esempio] [[L]] [[L]]] [[L]]]]] [[L'esempio] [[L]]]] [[L'esempio]]]] [[L'esempio]]]]] [[[L'esempio]]]]]]] [[[[[L'insieme]]]]]]]]]]] [[[[[[[[[[[[[[[[[L'insieme]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]][[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[

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;

Addetto con rilevamento overflow

Quando si utilizzano signed[]] numeri, il overflow si verifica quando il segno del risultato non corrisponde al segno previsto in base agli input.

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;

Subtrasportatori in VHDL

La sottotrazione può essere implementata sia mediante l'uso diretto del ]-] operatore o aggiungendo il complemento dei due al minuend. Mentre la modellazione comportamentale è semplice, la comprensione della propagazione del prestito e la gestione dei risultati negativi è essenziale.

Subtrattore comportamentale diretto

Il subtrattore più semplice utilizza l'operatore - con tipi non firmati o non firmati. Per la sottotrazione non firmata, il risultato può diventare negativo se B > A; in tali casi è necessario rilevare un sottoflusso (borrow). L'esempio seguente restituisce sia la differenza che una bandiera di prestito:

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;

Notare la conversione a ]firmato[] per il calcolo intermedio; questo permette una corretta gestione delle differenze negative.

Sottotrattore che utilizza il completamento di due

In alternativa, è possibile implementare la sottrazione aggiungendo il complemento di due di B. Questa tecnica è comune quando si riutilizza una scala esistente in un ALU. Il complemento di due di B è calcolato come (non B) + 1.

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

Entrambi i metodi sono di sintesi; selezionare quello che corrisponde alle preferenze architettoniche del vostro design.

Confronto e sottrazione

I sottraenti sono spesso utilizzati per implementare i comparatori. Esaminando il prestito o il segno della differenza, è possibile determinare se A > B, A < B, o A = B senza un comparatore dedicato. Ad esempio, dopo la sottrazione, se il risultato è zero (tutte le bit 0), gli input sono uguali. Se il prestito/segno è 1, allora A < B.

Moltiplicatori in VHDL

VHDL supporta l'operatore *[] per tipi non firmati e firmati, che inferisce un moltiplicatore combinato. Tuttavia, per larghezze di bit più grandi, i moltiplicatori combinati possono consumare logica significativa e avere ritardi di propagazione lunghi.

Multiplier combinato

Un moltiplicatore a 4 bit che utilizza l'operatore *[ è banale:

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;

Questo fa riferimento ad un moltiplicatore combinato, che in un FPGA viene tipicamente implementato utilizzando fette DSP dedicate (come blocchi Xilinx DSP48) o logica basata su LUT. Per larghezze fino a 18 bit, la maggior parte degli strumenti FPGA possono mappare la moltiplicazione a una singola fetta DSP. Per moltiplicatori più ampi, lo strumento di sintesi può combinare più fette DSP o usare una logica morbida.

Multiplier sequenziale (Shift-and-Add)

Per i disegni contrattati nell'area o quando il ritardo combinato è inaccettabile, un moltiplicatore sequenziale che si può usare per i bit. L'algoritmo classico a turni e add moltiplica due numeri N-bit sui cicli N dell'orologio.

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;

Questo design utilizza un'aggiunta a L-bit per ciclo di clock (4 cicli per ingressi a 4 bit), che consente di risparmiare area, ma sacrifica la produttività e la latenza.

Pinza multiplier

Per applicazioni ad alto rendimento, un moltiplicatore conduttivo inserisce i registri tra le fasi della moltiplicazione combinata. Molti strumenti di sintesi FPGA possono condutturare automaticamente un moltiplicatore quando si aggiungono i registri delle tubazioni. Ad esempio, utilizzando un per ... generare loop o inserimento manuale della fase:

-- 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;

Questo semplice approccio a due stadi raddoppia il throughput (un risultato per orologio dopo la latenza iniziale) aggiungendo un solo strato di registro in più.

Utilizzo delle DSP

In VHDL, utilizzando l'operatore *]] spesso automaticamente inferisce questi blocchi. Per garantire l'inferenza DSP, seguire le linee guida del fornitore: mantenere gli operandi all'interno della larghezza della fetta (ad esempio, 18x18, 18x25), evitare grandi operatori relazionali sul risultato, e utilizzare la larghezza appropriata

Xilinx Vivado Guida Sintesi[

Ottimizzazione Tecniche e Considerazioni Sintesi

Quando si implementano operazioni aritmetiche in VHDL, diversi fattori influiscono sulla qualità dei risultati:

  • Data Width:[] Usa la larghezza più piccola necessaria per ridurre la logica. Ad esempio, se gli input sono a 5 bit, usa unsigned(4 downto 0)].
  • ]Attribuzioni Sintesi:[] Applicare attributi come ] ], use dsp, o mult style]] per influenzare la mappatura.
  • Condivisione delle risorse:[] Se più operazioni utilizzano la stessa scala o moltiplicatore, considerare il riutilizzo dell'hardware tramite un componente condiviso o un singolo blocco aritmetico con ingressi multipli.
  • Pipelining:[] Inserisci i registri per soddisfare i vincoli di tempismo. Per le catene additive, bilanciare il posizionamento del registro per evitare lunghi percorsi combinatori.
  • Signed vs Unsigned:[]] Usa [[]]] [[] per operazioni firmate; lo strumento di sintesi tratterà la MSB come un bit di segno, che interessa la logica aritmetica inferredita.
  • Cate di trasporto:[] Per gli adder più ampi, la catena di trasporto è una risorsa dedicata in FPGAs. Assicurare che il vostro strumento di sintesi non sta rompendo la catena utilizzando segnali intermedi inappropriati.

Combinazione delle operazioni: ALU Esempio

Per illustrare come gli adders, i sottraenti e i moltiplicatori si integrano in un design più ampio, si consideri una semplice unità di logica aritmetica (ALU) che può aggiungere, sottrarre o moltiplicare due valori a 8 bit basati su un segnale selezionato:

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;

Questo ALU riutilizza lo stesso registro dei risultati e combina le tre operazioni. In sintesi, ogni operazione viene implementata come blocco separato, con l'output selezionato da un multiplexer. A seconda del dispositivo di destinazione, il moltiplicatore può essere il percorso critico.

Utilizzo di core IP per l'aritmetica complessa

Per operazioni avanzate (ad esempio, punto mobile, radice quadrata, modulo), o quando è necessario il massimo delle prestazioni, è consigliabile utilizzare core IP forniti dal fornitore. Questi sono altamente ottimizzati e hanno modelli di simulazione verificati. In VHDL, istanzia un core IP come componente, mappando i segnali alle sue porte.

  • Xilinx Floating-Point Operator[[]] per aggiungere/sub/multiply/dividere in formato IEEE 754.
  • Altera (Intel) ALTMULT ADD[[] per operazioni a più tempi.
  • Divider di retice[] per la divisione a punto fisso.

Utilizzando i core IP minimizza il rischio e spesso si traduce in prestazioni migliori rispetto agli equivalenti codificati a mano.

Intel FPGA IP Cores Guide[

Test e verifica

Scrivere le prove che esercitano i casi di angolo: overflow, zero, valori massimi e segni misti (per i tipi firmati). Per i moltiplicatori, testare tutte le combinazioni dei più piccoli input per verificare l'algoritmo. Utilizzare il assert]]] dichiarazione per controllare i risultati attesi. Esempio banco prova snippet per una scala a 4 bit:

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;

Per i disegni più grandi, considerare l'utilizzo di stimoli casuali e modelli d'oro in linguaggi di scrittura (Python, Tcl) per generare vettori di prova.

Tecniche di banco di prova VHDL (SynthWorks)

Conclusioni

L'implementazione di operazioni aritmetiche in VHDL è una miscela di comprensione digitale aritmetica, uso competente di tipi di dati e operatori, e la consapevolezza del comportamento degli strumenti di sintesi.Aggiunti e sottraenti sono semplici quando si utilizza numeric std, mentre i moltiplicatori richiedono un'attenta considerazione delle prestazioni e dell'area.

Per ulteriori informazioni, consultare il Manuale di riferimento linguistico IEEE VHDL e la documentazione specifica del fornitore sull'inferenza aritmetica.

IEEE Std 1076-2008 VHDL Manuale di riferimento linguistico[