library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use ieee.math_real.all; entity WSDriver is generic ( F_CLK : natural; -- Board frequency in Hz N_LED_MAX : natural -- Maximum number of LEDs to instantiate ); port ( clk : in std_logic; rst_n : in std_logic; -- LED address and LED value led_wr_in : in std_logic; led_in : in std_logic_vector(23 downto 0); addr_in : in std_logic_vector(integer(ceil(log2(real(N_LED_MAX)))) - 1 downto 0); -- Write-enable and value of N LEDs to keep active/addressable n_wr_in : in std_logic; n_in : in std_logic_vector(integer(ceil(log2(real(N_LED_MAX)))) - 1 downto 0); -- Output 1-bit line for WS2812 strip ws_out : out std_logic; -- Output Ready ready_out : out std_logic ); end WSDriver; architecture Behavioral of WSDriver is constant F_WS : real := 1.0/((0.3)*10**(-6.0)); constant T_RES_US : real := 50.0*10**(-6.0); constant RES_TICKS : integer := integer(ceil(T_RES_US*F_WS)); constant BITS_PER_LED : integer := 24; subtype LED is std_logic_vector(BITS_PER_LED - 1 downto 0); type LED_array is array (natural range 0 to N_LED_MAX - 1) of LED; type State is (ready, tx, reset); signal ws_clk : std_logic; signal led_idx_reg, led_idx_next : natural range 0 to N_LED_MAX - 1; signal bit_idx_reg, bit_idx_next : natural range 0 to N_LED_MAX - 1; signal state_reg, state_next : State; constant WS_CLKS_PER_BIT : integer := 4; signal ws_cntr_reg, ws_cntr_next : integer range WS_CLKS_PER_BIT - 1 downto 0; signal ws_en : std_logic; signal leds_reg, leds_next : LED_array; -- Register for maintaining the set of driven LEDs signal n_leds_reg, n_leds_next : unsigned(integer(ceil(log2(real(N_LED_MAX)))) - 1 downto 0) := (others => '0'); -- Currently accessed LED signal current_led : LED; -- Trailing reset counter signal reset_cntr_reg, reset_cntr_next : integer range RES_TICKS - 1 downto 0; -- Detecting wr_in assertions signal wr_in_detec_reg, wr_in_detec_next : std_logic := '0'; begin -- Next-state LED and Bit index process process (all) begin bit_idx_next <= bit_idx_reg; led_idx_next <= led_idx_reg; reset_cntr_next <= reset_cntr_reg; state_next <= state_reg; wr_in_detec_next <= wr_in_detec_reg; -- Latch wr_in value, to ensure re-transition to tx state if wr_in occured -- during a non-ready state. if led_wr_in = '1' then wr_in_detec_next <= '1'; end if; case state_reg is -- STATE READY when ready => bit_idx_next <= BITS_PER_LED - 1; led_idx_next <= 0; if n_leds_reg /= 0 and wr_in_detec_reg = '1' then wr_in_detec_next <= '0'; state_next <= tx; end if; -- STATE TX when tx => if ws_cntr_reg = (WS_CLKS_PER_BIT - 1) and ws_clk ='1' then if bit_idx_reg = 0 then -- Transition to next LED bit_idx_next <= BITS_PER_LED - 1; led_idx_next <= led_idx_reg + 1; else -- Bit was fully shifted, transition to next bit bit_idx_next <= bit_idx_reg - 1; end if; -- All LEDs fully shifted out (+last bit of last led)? transition to ready if bit_idx_reg = 0 and led_idx_reg + 1 = n_leds_reg then state_next <= reset; end if; end if; -- STATE TRAILING RESET when reset => if ws_clk ='1' then if reset_cntr_reg = RES_TICKS - 1 then reset_cntr_next <= 0; state_next <= ready; else reset_cntr_next <= reset_cntr_reg + 1; end if; end if; end case; end process; -- Currently accessed LED multiplexer current_led <= leds_reg(led_idx_reg) when state_reg = tx else (others => '0'); -- Next state WS counter process (state_reg, ws_cntr_reg, ws_clk) begin ws_cntr_next <= ws_cntr_reg; if state_reg = ready or state_reg = reset then ws_cntr_next <= 0; elsif ws_clk = '1' then if ws_cntr_reg /= (WS_CLKS_PER_BIT - 1) then ws_cntr_next <= ws_cntr_reg + 1; else ws_cntr_next <= 0; end if; end if; end process; -- Next-state LED values process(led_wr_in, led_in, leds_reg, addr_in, n_wr_in, n_in) begin leds_next <= leds_reg; n_leds_next <= n_leds_reg; if led_wr_in = '1' then leds_next(to_integer(unsigned(addr_in))) <= led_in; end if; -- Set N LEDs precedes LED write operation if n_wr_in = '1' then n_leds_next <= unsigned(n_in); end if; end process; -- WS output clock clkgen_ent : entity work.ClkGen generic map ( F_CLK => F_CLK, F_OUT => integer(F_WS) ) port map ( clk => clk, rst_n => rst_n, clk_o => ws_clk, en => ws_en ); -- Only enable ws clock gen when required ws_en <= '1' when state_reg /= ready else '0'; -- Clocking logic process(clk) begin if rising_edge(clk) then if rst_n = '0' then ws_cntr_reg <= 0; leds_reg <= (others => (others => '0')); n_leds_reg <= (others => '0'); wr_in_detec_reg <= '0'; bit_idx_reg <= BITS_PER_LED - 1; state_reg <= ready; led_idx_reg <= 0; reset_cntr_reg <= 0; else ws_cntr_reg <= ws_cntr_next; leds_reg <= leds_next; n_leds_reg <= n_leds_next; wr_in_detec_reg <= wr_in_detec_next; bit_idx_reg <= bit_idx_next; state_reg <= state_next; led_idx_reg <= led_idx_next; reset_cntr_reg <= reset_cntr_next; end if; end if; end process; -- WS2811 output bit sequence process(state_reg, ws_cntr_reg) begin ws_out <= '0'; if state_reg = tx then case ws_cntr_reg is when 0 => ws_out <= '1'; when 1 => ws_out <= current_led(bit_idx_reg); when 2 => ws_out <= '0'; when 3 => ws_out <= '0'; end case; end if; end process; ready_out <= '1' when state_reg = ready else '0'; end Behavioral;