Al escribir VHDL, recomiendo usar std_logic_vector (slv) en lugar de entero (int) para SEÑALES . (Por otro lado, usar int para genéricos, algunas constantes y algunas variables pueden ser muy útiles). En pocas palabras, si declara una señal de tipo int o tiene que especificar un rango para un entero, probablemente esté haciendo algo mal.
El problema con int es que el programador VHDL no tiene idea de cuál es la representación lógica interna de int, por lo que no podemos aprovecharla. Por ejemplo, si defino un int de rango de 1 a 10, no tengo idea de cómo el compilador codifica esos valores. Esperemos que se codifique como 4 bits, pero no sabemos mucho más allá de eso. Si pudiera sondear las señales dentro del FPGA, podría estar codificado como "0001" a "1010", o codificado como "0000" a "1001". También es posible que esté codificado de una manera que no tiene absolutamente ningún sentido para nosotros los humanos.
En su lugar, deberíamos usar slv en lugar de int, porque entonces tenemos control sobre la codificación y también tenemos acceso directo a los bits individuales. Tener acceso directo es importante, como verá más adelante.
Podríamos lanzar un int a slv siempre que necesitemos acceso a los bits individuales, pero eso se vuelve muy complicado, muy rápido. Eso es como obtener lo peor de ambos mundos en lugar de lo mejor de ambos mundos. Su código será difícil de optimizar para el compilador y casi imposible de leer. No recomiendo esto
Entonces, como dije, con slv tienes control sobre las codificaciones de bits y acceso directo a los bits. Entonces, ¿qué puedes hacer con esto? Te mostraré un par de ejemplos. Digamos que necesita emitir un pulso una vez cada 4,294,000,000 relojes. Así es como haría esto con int:
signal count :integer range 0 to 4293999999; -- a 32 bit integer
process (clk)
begin
if rising_edge(clk) then
if count = 4293999999 then -- The important line!
count <= 0;
pulse <= '1';
else
count <= count + 1;
pulse <= '0';
end if;
end if;
end process;
Y el mismo código usando slv:
use ieee.numeric_std.all;
signal count :std_logic_vector (32 downto 0); -- a 33 bit integer, one extra bit!
process (clk)
begin
if rising_edge(clk) then
if count(count'high)='1' then -- The important line!
count <= std_logic_vector(4293999999-1,count'length);
pulse <= '1';
else
count <= count - 1;
pulse <= '0';
end if;
end if;
end process;
La mayor parte de este código es idéntico entre int y slv, al menos en el sentido del tamaño y la velocidad de la lógica resultante. Por supuesto, uno está contando y el otro está contando, pero eso no es importante para este ejemplo.
La diferencia está en "la línea importante".
Con el ejemplo int, esto dará como resultado un comparador de 32 entradas. Con las LUT de 4 entradas que usa el Xilinx Spartan-3, esto requerirá 11 LUT y 3 niveles de lógica. Algunos compiladores pueden convertir esto en una resta que utilizará la cadena de transporte y abarcará el equivalente de 32 LUT, pero podría ejecutarse más rápido que 3 niveles de lógica.
Con el ejemplo de slv, no hay comparación de 32 bits, por lo que es "cero LUT, cero niveles de lógica". La única pena es que nuestro contador es un bit extra. Debido a que el tiempo adicional para este bit adicional de contador está en la cadena de transporte, hay un retraso de tiempo adicional "casi cero".
Por supuesto, este es un ejemplo extremo, ya que la mayoría de las personas no usarían un contador de 32 bits de esta manera. Se aplica a contadores más pequeños, pero la diferencia será menos dramática aunque aún significativa.
Este es solo un ejemplo de cómo utilizar slv sobre int para obtener una sincronización más rápida. Hay muchas otras formas de utilizar slv: solo se necesita un poco de imaginación.
Actualización: Se agregaron elementos para abordar los comentarios de Martin Thompson sobre el uso de int con "if (count-1) <0"
(Nota: supongo que quiso decir "if count <0", ya que eso lo haría más equivalente a mi versión slv y eliminaría la necesidad de esa resta adicional).
En algunas circunstancias, esto podría generar la implementación lógica prevista, pero no se garantiza que funcione todo el tiempo. Dependerá de su código y de cómo su compilador codifica el valor int.
Dependiendo de su compilador y de cómo especifique el rango de su int, es completamente posible que un valor int de cero no se codifique en un vector de bits de "0000 ... 0000" cuando se convierte en la lógica FPGA. Para que su variación funcione, debe codificar a "0000 ... 0000".
Por ejemplo, supongamos que define un int para tener un rango de -5 a +5. Espera que un valor de 0 se codifique en 4 bits como "0000", y +5 como "0101" y -5 como "1011". Este es el típico esquema de codificación de dos complementos.
Pero no asuma que el compilador usará dos complementos. Aunque inusual, los complementos podrían resultar en una "mejor" lógica. O bien, el compilador podría usar una especie de codificación "sesgada" donde -5 se codifica como "0000", 0 como "0101" y +5 como "1010".
Si la codificación del int es "correcta", entonces el compilador probablemente inferirá qué hacer con el bit de acarreo. Pero si es incorrecto, entonces la lógica resultante será horrible.
Es posible que usar un int de esta manera pueda dar como resultado un tamaño lógico y una velocidad razonables, pero no es una garantía. Cambiar a un compilador diferente (XST a Sinopsis, por ejemplo), o ir a una arquitectura FPGA diferente podría causar que ocurra exactamente lo incorrecto.
Unsigned / Signed vs. slv es otro debate más. Puede agradecer al comité del gobierno de los Estados Unidos por darnos tantas opciones en VHDL. :) Uso slv porque ese es el estándar para la interfaz entre módulos y núcleos. Aparte de eso, y algunos otros casos en simulaciones, no creo que haya un gran beneficio al usar slv sobre firmado / no firmado. Tampoco estoy seguro de si las señales firmadas / no compatibles son compatibles con las señales de tres estados.