Luces de Navidad controladas por FPGA 
Como cada año por estas fechas hago una revisita al concepto de las luces para el belén usando diferentes tecnologías. En este caso y como no podía ser de otro modo, intentaremos usar una FPGA para implementar este efecto.

Aspectos funcionales

Partimos de 5 leds de alta luminosidad (los mismos de años anteriores) y necesitamos que parpadeen de forma aleatoria, como si simularan el aspecto de una porción del cielo nocturno. La secuencia de parpadeo debería ser lo más aleatoria posible y lo ideal es que la probabilidad de parpadeo sea controlable para simular un ciclo día-noche.

Diseño

Para generar una secuencia de números pseudoaleatorios la forma más sencilla es utilizar un LFSR con la cantidad suficiente de bits como para dar la percepción de que se trata de un generador de números realmente aleatorios. Si partimos de un LFSR de 10 bits, para que sea maximal (que su secuencia numérica sea lo más larga posible antes de “dar la vuelta”) debemos implementar el siguiente polinomio de realimentación:
$$x^{10} + x^7 + 1$$
Este polinomio de realimentación garantiza una secuencia maximal de $2^{n} - 1$ valores, siendo en este caso $n=10$. La secuencia no es de $2^{n}$ valores debido a que el valor 0 (todos los bits a cero) no está incluido en la secuencia.

("=1" denota la operación XOR en notación IEC) La ruta de datos que se va a usar es la siguiente:

El funcionamiento interno sería el siguiente.

1. Se inicializa el LFSR (se le mete un valor que incluya, al menos un bit a 1).
2. Hacer 5 veces (una vez por cada uno de los 5 leds).
2.1. Se itera el LFSR para que genere el siguiente numero pseudoaleatorio.
2.2. Se empuja el bit resultante de la comparación entre el valor del LFSR (valor A) y una constante (valor B) en el registro de desplazamiento.
3. Se carga en el latch de salida el valor que hay en el registro de desplazamiento.
4. Se espera 1 segundo.
5. Saltar al paso 2.

Tanto para el conteo de la carga de los 5 bits en el registro de desplazamiento como para el conteo del tiempo de espera de 1 segundo se utiliza un contador de 32 bits de dos límites: uno de los límites se fija a 5 (para contar los bits) y otro de los límites se fija en 32000000 para contar 1 segundo (el reloj de la FPGA va a 32MHz).

A partir de este algoritmo se puede diseñar la siguiente máquina de estados:



Salidas de la FSM:
LFSR.RST = Reset del LFSR.
LFSR.ENA = Enable del LFSR.
SR.ENA = Enable del registro de desplazamiento.
LATCH.ENA = Enable del latch de salida.
CNT.RST = Reset del contador.
CNT.ENA = Enable del contador.

Entradas de la FSM:
CNT.T1 = a 1 cuando el contador llega a 5.
CNT.T2 = a 1 cuando el contador llega a 32000000.

Como se puede ver, se trata de un diseño totalmente síncrono, basado en enables y en el que se evita el uso de “gated clocks”, por lo tanto, perfectamente sintetizable en cualquier FPGA.

Por ahora la probabilidad de parpadeo está fijada por hardware como una constante (el valor de B en el diagrama, que no es modificable), sin embargo el diseño queda preparado para que en una siguiente versión se pueda obtener dicha constante de algún parámetro físico (ADC, reloj de tiempo real, etc.)

Implementación

La implementacion de todos los módulos se ha realizado siguiendo siempre un modelo RTL. A continuación se lista el codigo fuente de la unidad de más alto nivel (que se ha denominado “ChristmasLights”) y que engloba todos los submódulos (LFSR, comparador, registro de desplazamiento, latch, contador y FSM).

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity ChristmasLights is
	generic (
		NLeds : integer := 8;
		NWaitClocks : integer := 20;    -- for the simulation 20 clocks between lights change, but in real hardware change this value according FPGA clock
		Probability : integer := 512    -- 0 = all lights on, 1023 = all lights off
	);
	port (
		Clk   : in std_logic;
		Reset : in std_logic;
		Led   : out std_logic_vector((NLeds - 1) downto 0)
	);
end ChristmasLights;

architecture Architecture1 of ChristmasLights is
	component LFSR10 is
		port (
			Reset  : in std_logic;
			Enable : in std_logic;
			Clk    : in std_logic;
			Data   : out std_logic_vector(9 downto 0)
		);
	end component;
	component Comparator is
		generic (
			NBits : integer := 4
		);
		port (
			A           : in std_logic_vector((NBits - 1) downto 0);
			B           : in std_logic_vector((NBits - 1) downto 0);
			AGreatThanB : out std_logic;
			ALessThanB  : out std_logic;
			AEqualB     : out std_logic
		);
	end component;
	component ShiftRegister is
		generic (
			NBits : integer := 8
		);
		port (
			Enable         : in std_logic;
			Clk            : in std_logic;
			SerialInput    : in std_logic;
			ParallelOutput : out std_logic_vector((NBits - 1) downto 0)
		);
	end component;
	component Latch is
		generic (
			NBits : integer := 8
		);
		port (
			Enable  : in std_logic;
			Clk     : in std_logic;
			DataIn  : in std_logic_vector((NBits - 1) downto 0);
			DataOut : out std_logic_vector((NBits - 1) downto 0)
		);
	end component;
	component TwoLimitCounter is
		generic (
			NBits : integer := 4;
			Limit1 : integer := 3;
			Limit2 : integer := 2
		);
		port (
			Reset       : in std_logic;
			Enable      : in std_logic;
			Clock       : in std_logic;
			Terminated1 : out std_logic;
			Terminated2 : out std_logic
		);
	end component;
	signal LfsrEnable : std_logic;
	signal LfsrReset : std_logic;
	signal LfsrData : std_logic_vector(9 downto 0);
	signal CompOutput : std_logic;
	signal SREnable : std_logic;
	signal SRData : std_logic_vector((NLeds - 1) downto 0);
	signal LatEnable : std_logic;
	signal CntReset : std_logic;
	signal CntEnable : std_logic;
	signal CntBitsOut : std_logic;
	signal CntTimeOut : std_logic;
	signal FSMDBus : std_logic_vector(2 downto 0);
	signal FSMQBus : std_logic_vector(2 downto 0);
begin
	-- LFSR
	Lfsr : LFSR10 port map (
		Clk => Clk,
		Enable => LfsrEnable,
		Reset => LfsrReset,
		Data => LfsrData
	);
	-- comparator
	Comp : Comparator generic map (
		NBits => 10
	)
	port map (
		A => LfsrData,
		B => std_logic_vector(to_unsigned(Probability, 10)),
		AGreatThanB => CompOutput
	);
	-- shift register
	SR : ShiftRegister generic map (
		NBits => NLeds
	)
	port map (
		Enable => SREnable,
		Clk => Clk,
		SerialInput => CompOutput,
		ParallelOutput => SRData
	);
	-- output latch
	Lat : Latch generic map (
		NBits => NLeds
	)
	port map (
		Enable => LatEnable,
		Clk => Clk,
		DataIn => SRData,
		DataOut => Led
	);
	-- two limit counter
	Cnt : TwoLimitCounter generic map (
		NBits => 32,
		Limit1 => NLeds,
		Limit2 => NWaitClocks
	)
	port map (
		Reset => CntReset,
		Enable => CntEnable,
		Clock => Clk,
		Terminated1 => CntBitsOut,
		Terminated2 => CntTimeOut
	);

	-- FSM D FFs
	process (Clk, Reset)
	begin
		if (Clk'event and (Clk = '1')) then
			if (Reset = '1') then
				FSMQBus <= (others => '0');
			else
				FSMQBus <= FSMDBus;
			end if;
		end if;
	end process;
	-- FSM next state logic
	FSMDBus <= "000" when (Reset = '1') else
	           "001" when (FSMQBus = "000") else
	           "010" when (FSMQBus = "001") or (FSMQBus = "011") else
	           "011" when (FSMQBus = "010") and (CntBitsOut = '0') else
	           "100" when (FSMQBus = "010") and (CntBitsOut = '1') else
	           "101" when (FSMQBus = "100") or ((FSMQBus = "101") and (CntTimeOut = '0')) else
	           "001" when (FSMQBus = "101") and (CntTimeOut = '1') else
	           "000";
	-- FSM output logic
	LfsrReset <= '1' when (FSMQBus = "000") else
	             '0';
	CntReset <= '1' when (FSMQBus = "001") or (FSMQBus = "100") else
	            '0';
	CntEnable <= '1' when (FSMQBus = "010") or (FSMQBus = "101") else
	             '0';
	LfsrEnable <= '1' when (FSMQBus = "010") else
	              '0';
	SREnable <= '1' when (FSMQBus = "011") else
	            '0';
	LatEnable <= '1' when (FSMQBus = "100") else
	             '0';
end Architecture1;

Vídeo con el código VHDL implementado sobre la FPGA Spartan3E de Xilinx.



Todo el codigo puede descargarse de la sección soft. Feliz programación y feliz Navidad :-).

Comentarios 
Lo sentimos. No se permiten nuevos comentarios después de 90 días.