Table of Contents
Comprender las operaciones de Aritmética en VHDL
VHDL (VHSIC Hardware Descripción Language) proporciona un soporte robusto para implementar operaciones aritméticas, lo que hace de ella una piedra angular para diseñar sistemas digitales como procesadores, procesadores de señales digitales (DSPs) y unidades de control. bloques rítmicos como adiciones, subtractores y multiplicadores son bloques de construcción fundamentales, y saber cómo implementarlos eficientemente es crítico para simulación y síntesis.
Antes de bucear en componentes específicos, es esencial entender los dos paquetes clave VHDL que permiten aritmética: ieee.numeric std y ieee.std logic unsigned. El enfoque recomendado para nuevos diseños es utilizar
Todos los ejemplos de código en este artículo se escriben usando IEEE 1076-2008 compatible VHDL y dispositivos de destino Xilinx o Intel (Altera) FPGA, pero los conceptos se aplican a cualquier flujo de diseño digital.
Aceros en VHDL
La adición es la operación aritmética más fundamental. En VHDL, se pueden implementar adiciones en varios niveles de abstracción: conductual (utilizando el +] operador), flujo de datos (utilizando la asignación de señal concurrente), o estructural (inteligiendo componentes de menor nivel). Para la mayoría de los diseños prácticos, modelación conductual con numeric
Agregador de carretilla simple
Una arigen de carrete ondulada encadena las escaleras completas, donde el desempeñó cada bit alimenta el desplegable del siguiente bit superior. El siguiente ejemplo muestra una arigen de carrete ondulada de 4 bits que utiliza un proceso conductual:
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;
Puntos clave:
- La concatenación ['0' ' A) extiende las entradas a 5 bits, capturando el desvío.
- Utilizando directamente con el +] operador es sintetizable; la herramienta inferirá la lógica de la adder apropiada (carry, port-lookahead, o LUT-basada en FPGAs).
- La lista de sensibilidad del proceso incluye todas las señales de entrada, asegurando las actualizaciones de salida inmediatamente en cualquier cambio (comportamiento internacional).
Carry-Lookahead Adder (CLA)
[LT] [LT] [FLT] [L] [L]] [L] [L]] [L]] [L]] [L]] [L]] [L]] [L]] [L] [L]]
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;
Agregador con Detección de Desbordamiento
Al utilizar números firmados], la desbordamiento se produce cuando el signo del resultado no coincide con el signo esperado basado en los insumos. Detectar el flujo es esencial en el procesador ALUs. El siguiente fragmento muestra una escalera firmada con detección de desbordamiento:
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;
Subtractores en VHDL
La resta puede ser implementada ya sea mediante el uso directo del operador - o mediante la adición del complemento de dos del subtrahend al minuend. Mientras que el modelado conductual es sencillo, entender la propagación prestada y manejar resultados negativos es esencial.
Subtractor de conducta directa
El subtractor más simple utiliza el - operador con tipos no firmados o no firmados. Para la resta no firmada, el resultado puede ser negativo si B √≥ A; en tales casos, necesitamos detectar un subflujo (borrow). El siguiente ejemplo devuelve tanto la diferencia como una bandera prestada:
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;
Observe la conversión a firmada] para la computación intermedia; esto permite el manejo adecuado de las diferencias negativas. La parte más significativa (temp diff(4)) actúa como la bandera prestada.
Subtractor Usando el Complemento de Dos
Alternativamente, se puede implementar la resta añadiendo el complemento de B de los dos. Esta técnica es común al reutilizar una escalera existente en un ALU. El complemento de B de los dos se computa como (no B) + 1. Aquí está el concepto:
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 métodos son amigables con la síntesis; seleccione el que coincide con las preferencias arquitectónicas de su diseño.
Comparación y Sutracción
Los subtractores se utilizan a menudo para implementar comparadores. Al examinar el préstamo o signo de la diferencia, puede determinar si A > B, A < B, o A = B sin una comparación específica. Por ejemplo, después de la resta, si el resultado es cero (todas las partes 0), las entradas son iguales. Si el préstamo/sign es 1, entonces A < B.
Multiplicadores en VHDL
La multiplicación es más intensiva que la adición o la resta. VHDL apoya al operador * para tipos no firmados y firmados, que infiere un multiplicador combinado. Sin embargo, para anchos de bit más grandes, los multiplicadores combinados pueden consumir lógica significativa y tener demoras de propagación largas.
Multiplicador combinado
Un multiplicador de 4 bits que utiliza el * operador es 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;
Esto infiere un multiplicador combinado, que en un FPGA se implementa normalmente utilizando rebanadas DSP dedicadas (como bloques Xilinx DSP48) o lógica basada en LUT. Para anchos de hasta 18 bits, la mayoría de las herramientas FPGA pueden mapear la multiplicación a una sola rebanada DSP. Para multiplicadores más amplios, la herramienta de síntesis puede combinar múltiples rebanadas DSP o utilizar la lógica suave.
Multiplicador secuencial (Shift-and-Add)
Para diseños contiguas o cuando la demora combinada es inaceptable, un multiplicador secuencial que se acumula sobre bits se puede utilizar. El algoritmo clásico de cambio y agregado multiplica dos números N-bit sobre los ciclos de relojes N. A continuación se muestra un ejemplo simplificado (4-bit multiplicador, sin firmar, con señales de control omitidas para claridad):
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 diseño utiliza una adición L-bit por ciclo de reloj (4 ciclos para entradas de 4 bits). Guarda el área pero sacrifica la rentabilidad y la latencia.
Multiplicador de tuberías
Para aplicaciones de alto rendimiento, un multiplicador de tubos de carga se registra entre etapas de la multiplicación combinada. Muchas herramientas de síntesis de FPGA pueden trazar automáticamente un multiplicador cuando se agregan registros de tuberías. Por ejemplo, mediante un para ... generar ] lazo o la inserción manual de la etapa:
-- 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;
Este sencillo enfoque de dos etapas duplica la rendimiento (un resultado por reloj después de la latencia inicial) al tiempo que se añade una capa de registro adicional. Se pueden añadir más etapas para frecuencias de reloj más altas.
Usando Secciones DSP
Los FPGA modernos contienen rodajas DSP endurecidas configuradas para la multiplicación y acumulación. En VHDL, utilizando el operador * a menudo inferye automáticamente estos bloques. Para asegurar la inferencia DSP, siga las pautas de proveedor instantáneas: mantenga los operados dentro del ancho de la rebanada (por ejemplo, 18x18, 18x25), evite a grandes operadores relacionales en el resultado
Técnicas de optimización y Consideraciones de síntesis
Al implementar operaciones aritméticas en VHDL, varios factores afectan la calidad de los resultados:
- Ancho de datos:] Usar la anchura más pequeña necesaria para reducir la lógica. Por ejemplo, si los insumos son de 5 bits, use unsigned(4 downto 0)].
- ]Síntesis Atributos: Aplicar atributos como ]keep, use dsp, o mult style para influir en la cartografía.
- Resource Sharing: Si múltiples operaciones usan la misma adder o multiplicador, considere la reutilización de hardware a través de un componente compartido o un bloque aritmético único con entradas multiplexadas.
- Pipelining: Insertar registros para cumplir con las limitaciones de tiempo. Para las cadenas aditivas, equilibrar la colocación del registro para evitar largos caminos combinatorios.
- Firmado vs Unsigned: Usa ] firmada para operaciones firmadas; la herramienta de síntesis tratará al MSB como un bit de señal, afectando la lógica aritmética inferida.
- Carry Chains: Para los adidores anchos, la cadena de carga es un recurso dedicado en FPGAs. Asegúrese de que su herramienta de síntesis no está rompiendo la cadena utilizando señales intermedias inapropiadas.
Operaciones de combinación: Ejemplo ALU
Para ilustrar cómo los adidores, subtractores y multiplicadores se integran en un diseño más grande, considere una unidad lógica Aritmética simple (ALU) que puede añadir, substraer o multiplicar dos valores de 8 bits basados en una señal selecta:
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 el mismo registro de resultados y combina las tres operaciones. En síntesis, cada operación se implementa como un bloque separado, con la salida seleccionada por un multiplexor. Dependiendo del dispositivo objetivo, el multiplicador puede ser el camino crítico.
Utilizando los núcleos IP para la Aritmética Complejos
Para operaciones avanzadas (por ejemplo, punto flotante, raíz cuadrada, modulo), o cuando se necesita el máximo rendimiento, es recomendable utilizar núcleos IP proporcionados por proveedores. Estos son modelos de simulación altamente optimizados y verificados. En VHDL, instantáneamente un núcleo IP como componente, mapeando sus señales a sus puertos.
- Xilinx Operador de pintura flotante para añadir/sub/multiply/divide en formato IEEE 754.
- Altera (Intel) ALTMULT ADD] para operaciones multiadd.
- Dividente de celo para división de puntos fijos.
Utilizar núcleos IP minimiza el riesgo y a menudo resulta en un mejor rendimiento que los equivalentes codificados a mano. Consulte la documentación del proveedor para plantillas de instantáneas.
Pruebas y verificación
La simulación es crítica para los diseños aritméticos. Escribe testbenches que ejercen los casos de esquina: desbordamiento, cero, máximos valores y signos mixtos (para tipos firmados). Para multiplicadores, prueba todas las combinaciones de los insumos más pequeños para verificar el algoritmo. Usa la declaración para comprobar los resultados esperados.
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 diseños más grandes, considere usar estímulos aleatorios y modelos dorados en lenguajes de scripting (Python, Tcl) para generar vectores de prueba.
Técnicas de Testbench VHDL (SynthWorks)
Conclusión
Implementar operaciones aritméticas en VHDL es una mezcla de comprensión de aritmética digital, uso eficiente de tipos de datos y operadores, y conciencia de comportamiento de herramientas de síntesis. Los adiestradores y subtractores son sencillos cuando se usa numeric std, mientras que los multiplicadores requieren una cuidadosa consideración de rendimiento y área.
Para más lectura, consulte el Manual de referencia de idiomas IEEE VHDL y la documentación específica de proveedores sobre inferencia aritmética.