-- splink, ethernet-connected LED controller -- Copyright (C) 2022 xiretza -- -- This program is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as published by -- the Free Software Foundation, either version 3 of the License, or -- (at your option) any later version. -- -- This program is distributed in the hope that it will be useful, -- but WITHOUT ANY WARRANTY; without even the implied warranty of -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -- GNU Affero General Public License for more details. -- -- You should have received a copy of the GNU Affero General Public License -- along with this program. If not, see . library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use work.ws2812_pkg.color_t; use work.ws2812_pkg.colors_vector; entity splink is generic ( NUM_STRANDS : positive; MAX_STRAND_LEN : positive := 256 ); port ( clk : in std_logic; reset : in std_logic; udp_length : in std_logic_vector(15 downto 0); udp_valid : in std_logic; udp_last : in std_logic; udp_data : in std_logic_vector(31 downto 0); frame_number : out unsigned(31 downto 0); drivers : out std_logic_vector(NUM_STRANDS-1 downto 0) ); end entity; architecture a of splink is constant BITS_PER_LED: natural := 24; signal led_addr : std_logic_vector(7 downto 0); signal led_data_a : std_logic_vector(BITS_PER_LED * NUM_STRANDS - 1 downto 0); signal led_data_b : std_logic_vector(BITS_PER_LED * NUM_STRANDS - 1 downto 0); signal led_data : std_logic_vector(BITS_PER_LED * NUM_STRANDS - 1 downto 0); signal led_colors : colors_vector(NUM_STRANDS-1 downto 0); signal active_strand: natural range 0 to NUM_STRANDS-1; signal num_pixels: natural range 1 to MAX_STRAND_LEN; signal current_frame: unsigned(31 downto 0); signal pixels_received: natural range 0 to MAX_STRAND_LEN-1; signal run : std_logic; signal sender_done : std_logic; -- "PIXL" constant MAGIC_NUMBER : std_logic_vector(31 downto 0) := x"5049584c"; -- magic + frame num + strand num (4 bytes each) constant HEADER_LEN : natural := 12; type receive_state_t is (MAGIC, FRAME_NUM, STRAND_NUM, DATA, DROP); constant RESET_STATE : receive_state_t := MAGIC; signal receive_state : receive_state_t; type bank_t is (BANK_A, BANK_B); signal output_bank : bank_t; begin ws2812_inst: entity work.ws2812_parallel generic map ( NUM_DRIVERS => NUM_STRANDS, NUM_LEDS => MAX_STRAND_LEN, COLOR_ORDER => "GRB", T_CLK => 12.5 ns, T0H => 0.35 us, T0L => 0.9 us, T1H => 0.7 us, T1L => 0.55 us, T_RES => 100 us ) port map ( n_reset => not reset, clk => clk, run => run, done => sender_done, led_addr => led_addr, led_colors => led_colors, dout => drivers ); process(led_data) function make_color(data_in: std_logic_vector(BITS_PER_LED-1 downto 0)) return color_t is begin return ( red => data_in(23 downto 16), green => data_in(15 downto 8), blue => data_in(7 downto 0) ); end function; begin for i in 0 to NUM_STRANDS-1 loop led_colors(i) <= make_color(led_data((i+1) * BITS_PER_LED - 1 downto i * BITS_PER_LED)); end loop; end process; -- memory inference help with output_bank select led_data <= led_data_a when BANK_A, led_data_b when BANK_B; fsm: process(clk) type strand_buffer_t is array(0 to MAX_STRAND_LEN-1) of std_logic_vector(BITS_PER_LED * NUM_STRANDS - 1 downto 0); variable strand_buffer_a : strand_buffer_t; variable strand_buffer_b : strand_buffer_t; variable received_strands : std_logic_vector(NUM_STRANDS-1 downto 0); variable input_bank : bank_t; begin if rising_edge(clk) then led_data_a <= strand_buffer_a(to_integer(unsigned(led_addr))); led_data_b <= strand_buffer_b(to_integer(unsigned(led_addr))); if (and received_strands) and sender_done then output_bank <= input_bank; run <= '1'; case input_bank is when BANK_A => input_bank := BANK_B; when BANK_B => input_bank := BANK_A; end case; frame_number <= current_frame; received_strands := (others => '0'); else run <= '0'; end if; if reset then received_strands := (others => '0'); receive_state <= RESET_STATE; elsif udp_valid then if udp_last then -- always resynchronize to start of packet receive_state <= RESET_STATE; end if; case receive_state is when MAGIC => if udp_data /= MAGIC_NUMBER then receive_state <= DROP; elsif (unsigned(udp_length) - HEADER_LEN) / 4 > MAX_STRAND_LEN then receive_state <= DROP; else num_pixels <= (to_integer(unsigned(udp_length)) - HEADER_LEN) / 4; receive_state <= STRAND_NUM; end if; when STRAND_NUM => if unsigned(udp_data) >= NUM_STRANDS then receive_state <= DROP; else active_strand <= to_integer(unsigned(udp_data)); receive_state <= FRAME_NUM; end if; when FRAME_NUM => if not (or received_strands) then current_frame <= unsigned(udp_data); elsif current_frame /= unsigned(udp_data) then current_frame <= unsigned(udp_data); received_strands := (others => '0'); end if; pixels_received <= 0; receive_state <= DATA; when DATA => if pixels_received /= num_pixels - 1 then pixels_received <= pixels_received + 1; elsif udp_last then received_strands(active_strand) := '1'; else -- packet too long receive_state <= DROP; end if; when DROP => -- wait until udp_last end case; end if; -- workaround for ghdl#2078 if reset = '0' and udp_valid = '1' and receive_state = DATA then -- workaround for ghdl#2102 for i in 0 to NUM_STRANDS-1 loop if i = active_strand then if input_bank = BANK_A then strand_buffer_a(pixels_received)((i+1) * BITS_PER_LED - 1 downto i * BITS_PER_LED) := udp_data(23 downto 0); else strand_buffer_b(pixels_received)((i+1) * BITS_PER_LED - 1 downto i * BITS_PER_LED) := udp_data(23 downto 0); end if; end if; end loop; end if; end if; end process; end architecture;