From: rs <> Date: Mon, 15 Dec 2025 18:50:00 +0000 (-0600) Subject: Add I2S blocks and tests X-Git-Url: https://git.the-white-hart.net/?a=commitdiff_plain;h=7933fe53f9df032e0bdbfce606f1df5c78f34669;p=vhdl Add I2S blocks and tests --- diff --git a/libraries/dsp/i2s_input.vhd b/libraries/dsp/i2s_input.vhd new file mode 100644 index 0000000..60c53af --- /dev/null +++ b/libraries/dsp/i2s_input.vhd @@ -0,0 +1,82 @@ +-------------------------------------------------------------------------------- +-- i2s_input - receives a stream of samples from an I2S interface +-------------------------------------------------------------------------------- + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +library unisim; +use unisim.vcomponents.all; + + +entity i2s_input is + port ( + sck: in std_logic; + ws: in std_logic; + sd: in std_logic; + + stb_o: out std_logic; + rdy_i: in std_logic; + l_dat_o: out std_logic_vector(15 downto 0); + r_dat_o: out std_logic_vector(15 downto 0) + ); +end i2s_input; + + +architecture behavioral of i2s_input is + + signal shift_reg: std_logic_vector(15 downto 0); + signal ws_reg: std_logic; + signal ws2_reg: std_logic; + + signal latch_l: std_logic; + signal latch_r: std_logic; + + signal samp_l_reg: std_logic_vector(15 downto 0); + signal samp_r_reg: std_logic_vector(15 downto 0); + + signal stb_reg: std_logic; + +begin + + process (sck, ws) + begin + if rising_edge(sck) then + shift_reg <= shift_reg(14 downto 0) & sd; + ws2_reg <= ws_reg; + ws_reg <= ws; + end if; + end process; + + latch_l <= '1' when ws_reg = '1' and ws2_reg = '0' else '0'; + latch_r <= '1' when ws_reg = '0' and ws2_reg = '1' else '0'; + + process (sck, latch_l, latch_r, shift_reg) + begin + if rising_edge(sck) then + if latch_l = '1' then + samp_l_reg <= shift_reg; + end if; + if latch_r = '1' then + samp_r_reg <= shift_reg; + end if; + end if; + end process; + + process (sck, latch_r, rdy_i) + begin + if rising_edge(sck) then + if latch_r = '1' then + stb_reg <= '1'; + elsif rdy_i = '1' then + stb_reg <= '0'; + end if; + end if; + end process; + + stb_o <= stb_reg; + l_dat_o <= samp_l_reg; + r_dat_o <= samp_r_reg; + +end behavioral; diff --git a/libraries/dsp/i2s_output.vhd b/libraries/dsp/i2s_output.vhd new file mode 100644 index 0000000..7d1703f --- /dev/null +++ b/libraries/dsp/i2s_output.vhd @@ -0,0 +1,64 @@ +-------------------------------------------------------------------------------- +-- i2s_output - transmits a stream of samples to an I2S interface +-------------------------------------------------------------------------------- + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +library unisim; +use unisim.vcomponents.all; + + +entity i2s_output is + port ( + sck: in std_logic; + ws: in std_logic; + sd: out std_logic; + + stb_i: in std_logic; + rdy_o: out std_logic; + l_dat_i: in std_logic_vector(15 downto 0); + r_dat_i: in std_logic_vector(15 downto 0) + ); +end i2s_output; + + +architecture behavioral of i2s_output is + + signal ws_reg: std_logic; + signal shift_reg: std_logic_vector(15 downto 0); + + signal latch_l: std_logic; + signal latch_r: std_logic; + +begin + + sd <= shift_reg(15); + + process (sck, ws) + begin + if falling_edge(sck) then + ws_reg <= ws; + end if; + end process; + + latch_l <= '1' when ws = '0' and ws_reg = '1' else '0'; + latch_r <= '1' when ws = '1' and ws_reg = '0' else '0'; + + process (sck, latch_l, latch_r, l_dat_i, r_dat_i, shift_reg) + begin + if falling_edge(sck) then + if latch_l = '1' then + shift_reg <= l_dat_i; + elsif latch_r = '1' then + shift_reg <= r_dat_i; + else + shift_reg <= shift_reg(14 downto 0) & '0'; + end if; + end if; + end process; + + rdy_o <= latch_r; + +end behavioral; diff --git a/libraries/dsp/tests/test_i2s_ctrl.vhd b/libraries/dsp/tests/test_i2s_ctrl.vhd new file mode 100644 index 0000000..755af5a --- /dev/null +++ b/libraries/dsp/tests/test_i2s_ctrl.vhd @@ -0,0 +1,51 @@ +library ieee; +use ieee.std_logic_1164.all; + +library work; + + +entity test_i2s_ctrl is +end test_i2s_ctrl; + + +architecture behavior of test_i2s_ctrl is + + constant CLK_I_PERIOD: time := 20 ns; + + signal rst_i: std_logic; + signal clk_i: std_logic; + + signal ws: std_logic; + signal sck: std_logic; + +begin + + p_test: process + begin + -- Reset + rst_i <= '1'; + wait for CLK_I_PERIOD*4; + rst_i <= '0'; + + -- Done + wait; + end process; + + e_uut: entity work.i2s_ctrl + port map ( + rst_i => rst_i, + clk_50 => clk_i, + + ws => ws, + sck => sck + ); + + p_clk: process + begin + clk_i <= '0'; + wait for CLK_I_PERIOD/2; + clk_i <= '1'; + wait for CLK_I_PERIOD/2; + end process; + +end; diff --git a/libraries/dsp/tests/test_i2s_input.vhd b/libraries/dsp/tests/test_i2s_input.vhd new file mode 100644 index 0000000..8268ef7 --- /dev/null +++ b/libraries/dsp/tests/test_i2s_input.vhd @@ -0,0 +1,82 @@ +library ieee; +use ieee.std_logic_1164.all; + +library simulated; + +library work; + + +entity test_i2s_input is +end test_i2s_input; + + +architecture behavior of test_i2s_input is + + constant CLK_50_PERIOD: time := 20 ns; + + signal rst_i: std_logic; + signal clk_50: std_logic; + + signal sck: std_logic; + signal ws: std_logic; + signal sd: std_logic; + + signal stb: std_logic; + signal rdy: std_logic; + signal l_dat: std_logic_vector(15 downto 0); + signal r_dat: std_logic_vector(15 downto 0); + +begin + + p_test: process + begin + -- Initial values + rdy <= '1'; + + -- Reset + rst_i <= '1'; + wait for CLK_50_PERIOD*4; + rst_i <= '0'; + + -- Test + + -- Done + wait; + end process; + + e_uut: entity work.i2s_input + port map ( + sck => sck, + ws => ws, + sd => sd, + + stb_o => stb, + rdy_i => rdy, + l_dat_o => l_dat, + r_dat_o => r_dat + ); + + e_src: entity simulated.proto_i2s_tx + port map ( + sck => sck, + ws => ws, + sd => sd + ); + + e_ctrl: entity work.i2s_ctrl + port map ( + rst_i => rst_i, + clk_50 => clk_50, + ws => ws, + sck => sck + ); + + p_clk50: process + begin + clk_50 <= '0'; + wait for CLK_50_PERIOD/2; + clk_50 <= '1'; + wait for CLK_50_PERIOD/2; + end process; + +end; diff --git a/libraries/dsp/tests/test_i2s_output.vhd b/libraries/dsp/tests/test_i2s_output.vhd new file mode 100644 index 0000000..1ef06a1 --- /dev/null +++ b/libraries/dsp/tests/test_i2s_output.vhd @@ -0,0 +1,100 @@ +library ieee; +use ieee.std_logic_1164.all; + +library work; + + +entity test_i2s_output is +end test_i2s_output; + + +architecture behavior of test_i2s_output is + + constant CLK_50_PERIOD: time := 20 ns; + + signal rst_i: std_logic; + signal clk_50: std_logic; + + signal sck: std_logic; + signal ws: std_logic; + signal sd: std_logic; + + signal stb: std_logic; + signal rdy: std_logic; + signal l_dat: std_logic_vector(15 downto 0); + signal r_dat: std_logic_vector(15 downto 0); + + signal rx_stb: std_logic; + signal rx_l_dat: std_logic_vector(15 downto 0); + signal rx_r_dat: std_logic_vector(15 downto 0); + +begin + + e_test: process + begin + -- Initial values + + -- Reset + rst_i <= '1'; + wait for CLK_50_PERIOD*4; + rst_i <= '0'; + + -- Test + + -- Done + wait; + end process; + + e_ctrl: entity work.i2s_ctrl + port map ( + rst_i => rst_i, + clk_50 => clk_50, + sck => sck, + ws => ws + ); + + e_src: entity work.src_counter + generic map (WIDTH => 16) + port map ( + rst_i => rst_i, + clk_i => sck, + stb_o => stb, + rdy_i => rdy, + dat_o => l_dat + ); + r_dat <= l_dat; + + e_uut: entity work.i2s_output + port map ( + sck => sck, + ws => ws, + sd => sd, + stb_i => stb, + rdy_o => rdy, + l_dat_i => l_dat, + r_dat_i => r_dat + ); + + -- FIXME: one treats the left sample as first, the other treats the right sample as first + -- so this RX block receives staggered samples + -- Not a big deal for streaming audio, so not worth fixing now. + e_rx: entity work.i2s_input + port map ( + sck => sck, + ws => ws, + sd => sd, + stb_o => rx_stb, + rdy_i => '1', + l_dat_o => rx_l_dat, + r_dat_o => rx_r_dat + ); + + p_clk: process + begin + clk_50 <= '0'; + wait for CLK_50_PERIOD/2; + clk_50 <= '1'; + wait for CLK_50_PERIOD/2; + end process; + +end; diff --git a/libraries/simulated/proto_i2s_tx.vhd b/libraries/simulated/proto_i2s_tx.vhd new file mode 100644 index 0000000..83c1e86 --- /dev/null +++ b/libraries/simulated/proto_i2s_tx.vhd @@ -0,0 +1,58 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + + +entity proto_i2s_tx is + port ( + sck: in std_logic; + ws: in std_logic; + sd: out std_logic + ); +end proto_i2s_tx; + + +architecture behavioral of proto_i2s_tx is + + signal ws_reg: std_logic; + +begin + + process (sck, ws) + begin + if rising_edge(sck) then + ws_reg <= ws; + end if; + end process; + + process + variable i: integer := 0; + variable samp_l: std_logic_vector(15 downto 0); + variable samp_r: std_logic_vector(15 downto 0); + begin + wait until ws_reg = '0' or ws_reg = '1'; + while true loop + -- Get next samples + samp_l := std_logic_vector(to_signed(i, 16)); + samp_r := std_logic_vector(to_signed(i, 16)); + i := i + 1; + + -- Output left sample + while ws_reg = '0' loop + sd <= samp_l(15); + samp_l := samp_l(14 downto 0) & '0'; + wait until rising_edge(sck); + wait until falling_edge(sck); + end loop; + + -- Output right sample + while ws_reg = '1' loop + sd <= samp_r(15); + samp_r := samp_r(14 downto 0) & '0'; + wait until rising_edge(sck); + wait until falling_edge(sck); + end loop; + end loop; + end process; + +end behavioral;