[Aporte] Numeros aleatorios en VHDL

Hola amigos,

En esta serie de artículos voy a presentar varios ejemplos de VHDL, comenzando por un ejemplo simple de generador de números aleatorios, le iremos agregando más funciones, como ser, código VHDL de pruebas (test bench), parametrización del diseño, grabación de datos en disco y posterior análisis en Matlab, etc.

Para poder aprovechar este aporte deben conocer previamente el lenguaje VHDL y el manejo de Modelsim u otro simulador que Uds. prefieran.

Aquí vamos:

Vamos a generar números aleatorios por hardware. Una manera popular de generar números aleatorios por hardware es utilizar lo que se denomina LFSR, registros de desplazamiento con realimentación lineal. El registro y su implementación lineal son equivalentes al desarrollo de un polinomio, donde cada potencia del polinomio es representada por el orden del bit dentro del registro.

Como ni yo entiendo lo que dice en la frase anterior :D, supongo que con los ejemplos prácticos será más digerible.

A continuación, la primera versión del código VHDL que genera números aleatorios.

Código:
-- File name      : lfsr1.vhd
-- Comments    : Random number generator in VHDL - Version 1
-----------------------------------------------------------------
library ieee;
    use ieee.std_logic_1164.all;

entity lfsr1 is
  port (
    reset  : in  std_logic;
    clk    : in  std_logic;                    
    count  : out std_logic_vector (3 downto 0)
  );
end entity;

architecture rtl of lfsr1 is
    signal count_i    	: std_logic_vector (3 downto 0);
    signal feedback 	: std_logic;

begin
    feedback <= not(count_i(3) xor count_i(2));		-- LFSR size 4

    process (reset, clk) 
	begin
        if (reset = '1') then
            count_i <= (others=>'0');
        elsif (rising_edge(clk)) then
            count_i <= count_i(2 downto 0) & feedback;
        end if;
    end process;
    count <= count_i;
end architecture;

El código es cortito. La señal de feedback, en este primer ejemplo, está calculada tomando en cuenta un polinomio de orden 3 (4 bits), que genera una secuencia de 15 valores. Si generamos más valores que 15 la secuencia se repite indefinidamente.

Es por eso que a estas secuencias se las denomina pseudo aleatorias, por un lado presentan una cierta variabilidad, por otro, son repetitivas y si sabemos el orden del polinomio, a un valor determinado siempre le seguirá un valor conocido... Un número realmente aleatorio no se comporta así, pero igual veremos que la secuencia pseudo aleatoria es muy útil, por ejemplo, como generador de ruido blanco, o rosa.

En VHDL no se puede leer en el código el valor de una salida, es por eso que todos los cálculos los realizo con count_i, y solo al final vuelco su valor a la salida. Creo que es una buena costumbre y clarifica el código el utilizar esta convención de agregar el sufijo "_i" al nombre de la señal. Uds. pueden usar esa mismo convención u otra con la que se sientan cómodos, lo importante es adoptar una convención y después usarla siempre.

Dentro del proceso lo que estamos haciendo es un registro de desplazamiento cuya entrada (feedback) es una combinación lineal de algunos de los bits del registro mediante compuertas XOR.

En la próxima pondré el código del test bench y veremos algunas formas de onda.
 
Siguiendo con este pequeño tutorial sobre VHDL, en esta oportunidad incluyo el banco de pruebas, o testbench, para el contador pseudo aleatorio del mensaje anterior.

Código:
-- File Name   : tb_lfsr1.vhd
--  Comments   : Test bench for the random number generation
--				 Version 1
------------------------------------------------------------------
library ieee;
    use ieee.std_logic_1164.all;
    use ieee.std_logic_textio.all;
	use ieee.numeric_std.ALL;
    use std.textio.all;
    
entity tb_lfsr1 is
end entity;

architecture test of tb_lfsr1 is

    constant PERIOD  : time   := 20 ns;
	
    signal clk       : std_logic := '0';
    signal reset     : std_logic := '1';
    signal count     : std_logic_vector (3 downto 0);
    signal endSim	 : boolean   := false;

    component lfsr1 is
    port (
        reset     : in  std_logic;                       
        clk       : in  std_logic;               
        count     : out std_logic_vector (3 downto 0)   
    );
    end component;
    

begin
    clk     <= not clk after PERIOD/2;
    reset   <= '0' after  PERIOD*10;
	endSim  <= true after PERIOD*80;

	-- End the simulation
	process 
	begin
		if (endSim) then
			assert false 
				report "End of simulation." 
				severity failure; 
		end if;
		wait until (clk = '1');
	end process;	

    lfrs1_inst : lfsr1
    port map (
        clk      => clk,
        reset    => reset,
        count    => count
    );

end architecture;

El banco de pruebas incluye el elemento a ser verificado, que se define como componente. Además, definimos señales que nos ayudarán a excitar las entradas y observar las salidas.

Así, definimos un reloj de período 20 ns (50MHz), activamos el reset al comienzo de la prueba y lo desactivamos luego de un tiempo.

En este diseño tan simple, estando el reset inactivo y habiendo reloj, el circuito ya proporciona su salida.

He incluido además un proceso que culmina la simulación luego de un tiempo determinado mediante la señal endSim. Esto me permite correr la simulación mediante el comando run -all de Modelsim.

A continuación dos screenshots que muestran la salida del generador.
lfsr1.jpg

Aquí podemos ver la salida del generador que en cierta medida recuerda a una señal de ruido. La señal no es muy satisfactoria ya que la secuencia es de sólo 15 pasos. Como podemos ver la señal, luego de recorridos quince valores, se repite. O sea, es periódica. Otro impedimento si lo que queremos es una señal de ruido. En próximos aportes veremos como obtener señales de ruido mejores.

En la imagen siguiente podemos ver un período de la señal de pseudo ruido y como recorre los quince valores de la secuencia.

lfsr2.jpg


Hasta la proxima.
 
Vamos a mejorar un poco más nuestro generador de números aleatorios.

En general se prefiere no utilizar constantes en el cuerpo de nuestro programa, como las que definen el ancho del registro. El inconveniente de utilizar constantes es que si queremos cambiar nuestro programa, deberemos recordar actualizar todas y cada una de las ocurrencias del número constante... y es probable que cometamos un error al hacerlo. Por eso en lugar de utilizar números, se utilizan símbolos que representan a esos números constantes.

VHDL otorga al menos dos maneras de definir constantes. Una es el uso de GENERICS, y la otra es definir la constante en un paquete predefinido. Usaremos la segunda opción.

Esta versión del generador de pseudo-ruido, entonces, incluye un paquete de constantes predefinidas (por ahora tiene una sola constante), como vemos aquí

Código:
-- File Name   : lfsr_pkg.vhd
------------------------------------------------------------------
library ieee;
    use ieee.std_logic_1164.all;
    use ieee.std_logic_textio.all;
	use ieee.numeric_std.ALL;
    use std.textio.all;
    
package lfsr_pkg is
	
    constant LFSR_W : natural := 9;		-- LFSR width
	
end lfsr_pkg;


El código en su versión 1.1, usa constantes predefinidas. La cláusula "use" con el nombre del paquete es la que permite utilizar las constantes definidas en tal paquete.


Código:
-- File name     : lfsr1_1.vhd
-- 	Comments     : Random number generator in VHDL - Version 1.1
--
-- Revisions
--   v1:    Initial
--   v1.1:  Added enable signal
--          Constants defined on external package lfsr_pkg
-----------------------------------------------------------------
library ieee;
    use ieee.std_logic_1164.all;
	use work.lfsr_pkg.all;

entity lfsr1 is
  port (
    reset  : in  std_logic;
    clk    : in  std_logic; 
    enable : in  std_logic;                    		 -- Enable counting	
    count  : out std_logic_vector (LFSR_W-1 downto 0) -- lfsr output
  );
end entity;

architecture rtl of lfsr1 is
    signal count_i    	: std_logic_vector (LFSR_W-1 downto 0);
    signal feedback 	: std_logic;

begin
    feedback <= not(count_i(LFSR_W-1) xor count_i(LFSR_W-5));		-- LFSR size 9

    process (reset, clk) 
	begin
        if (reset = '1') then
            count_i <= (others=>'0');
        elsif (rising_edge(clk)) then
	    if (enable = '1') then
		count_i <= count_i(LFSR_W-2 downto 0) & feedback;
	    end if;	
        end if;
    end process;
    count <= count_i;
end architecture;

Qué otros cambios incluye esta versión?

1. Se agregó una señal de enable. Fíjense que es importante la manera de escribir código con enable, el estado de esta señal se verifica DENTRO del bucle condicionado al flanco creciente de la señal de reloj.

La señal de enable nos permitirá procesar señales de ruido a frecuencias diferentes de (aunque siempre menores o iguales que) la frecuencia de reloj.

2. Se cambió el tamaño del registro (ahora tiene 9 bits), y la lógica de generación de realimentación. La señal de pseudo ruido generada es mucho más parecido a lo que esperaríamos:


lfsr1_1.jpg

Notas:
a- Si miran con atención, verán que esta señal también es periódica. Pero el período es mucho más largo que el ejemplo analizado en los mensajes #1 y #2.
b- Como desafío para Uds., queda el adaptar el test bench a esta nueva versión. Se puede verificar que la secuencia para 9 bits es de 511 pasos, por lo que el período de esta señal de pseudo ruido es de 10220ns (clk 50MHz => 20ns para cada paso)
c- En el zip adjunto encontrarán las fuentes de los archivos de este capítulo

En la próxima veremos como exportar los datos del testbench a un archivo para analizarlos en Matlab.
 

Adjuntos

  • lfsr1_1.zip
    980 bytes · Visitas: 9
Última edición:
Hola nuevamente,

Seguimos en este tutorial con la grabación de datos a archivos, que luego analizaremos en Matlab. Si están siguiendo esta serie, en el mensaje número 3 propuse algunas tareas. Si piensan hacerlas no sigan leyendo ya que a continuación se presentan algunas de las soluciones a las preguntas del mensaje anterior.

En los mensajes siguientes sólo destacaré las diferencias con respecto a versiones anteriores para no pegar extensos (y repetidos) trozos de código. Se adjunta al mensaje los archivos completos.

Dicho sea de paso, como editor uso y recomiendo al Notepad++, que incluye números de líneas y reconoce la sintaxis de VHDL.

El siguiente proceso fue agregado al testbench para grabar datos de la simulación a un archivo:
Código:
	-- Save data to file
    process 
		file 		file_id: 	text;
		variable 	line_num: 	line;
		variable	cnt:		integer := 0;
	begin
		-- Open the file
		file_open(file_id, log_file, WRITE_MODE);
		wait until (enable = '1');
		wait until (clk = '1');
		
		-- Loop and write all values to a file		
		for cnt in 0 to 2047 loop
			write(line_num, to_integer(unsigned(count)) ); 
			writeline(file_id, line_num);
			wait until (clk = '1');
		end loop;
		
		file_close(file_id);
		endSim <= true;
		wait until (clk = '1');

	end process;

Los números de línea mencionados a continuación se refieren al archivo completo:
67 a 69: Se abre el archivo y se espera hasta que la señal enable y clock se hacen activas
72 a 76: Lazo en el que se escriben los datos al archivo. Noten el uso de función de conversión de std_logic_vector a unsigned y de ahí a entero(*), la escritura de este dato a una "línea" y de la línea al archivo. Se realiza una escritura por cada flanco ascendiente del clk.
78 a 80: Se cierra el archivo y se activa la bandera que termina la simulación

(*) VHDL soporta operaciones matemáticas con std_logic_vector. Pero cuando existe una duda sobre si tal vector tiene o no signo, se lo debe aclarar como en este caso en que decidimos tomar las salidas del LFSR como sin signo (unsigned).

El archivo generado res.log se ve así (fragmento):

Código:
0
1
3
7
15
31
62
124

y posee como era de esperarse 2048 líneas.


A continuación, la lista completa de los cambios de tb_lfsr1 a tb_lfsr1_1, según números de línea:

18: definición del nombre del archivo a grabar
22, 30, 39, 56: agregado el soporte a la nueva señal "enable"
60 a 82: nuevo proceso para grabación de archivos de datos de salida de la simulación
 

Adjuntos

  • tb_lfsr_1_1.zip
    1.9 KB · Visitas: 11
FINALE

Bueno, este es el último capítulo que tenia pensado para este mini tutorial.

En este capítulo tomamos los datos de salida de la simulación en VHDL y la analizamos en Matlab. A continuación el código que utilicé en Matlab para analizar en el campo frecuencial la salida del LFSR, en diversas configuraciones (con cada vez más bits). A medida que aumentan los bits, vemos que aumenta de forma significativa el desempeño del LFSR para crear una señal lo más parecida posible a ruido.

Código:
NFFT = 2048;
y = dlmread('res.log');

% Normalize the input
m = (max(y)-min(y))/2;
y = (y-m)/m;

Y = fft(y,NFFT)/NFFT;
f = linspace(0,1,NFFT/2+1);
subplot

% Plot single-sided amplitude spectrum.
subplot(1,2,1);
plot(f,20*log(abs(Y(1:NFFT/2+1)))) 
title('Single-Sided Amplitude Spectrum of y(t)')
xlabel('Frequency (Hz)')
ylabel('|Y(f)|')

% Plot time domain
subplot(1,2,2);
plot(y(1:250));
title('Time domain')
xlabel('time')

El código en Matlab normaliza los datos de entrada, y después simplemente genera el gráfico en frecuencia (con frecuencia también normalizada) de los generadores LFSR. También muestra un fragmento de la respuesta temporal.

A continuación los resultados para tamaños de registro de 5, 7, 9 y 11 bits:

LFSR5.jpg

LFSR7.jpg

LFSR9.jpg

LFSR11.jpg
 
Atrás
Arriba