From: rs <> Date: Mon, 12 Jan 2026 19:22:14 +0000 (-0600) Subject: Add WAV to/from I2S simulated PMOD device X-Git-Url: https://git.the-white-hart.net/?a=commitdiff_plain;h=090530703d9bf9ddcfe9eac2ca3325805b312ab3;p=vhdl Add WAV to/from I2S simulated PMOD device --- diff --git a/libraries/simulated/dev_pmod_i2s.vhd b/libraries/simulated/dev_pmod_i2s.vhd new file mode 100644 index 0000000..6e5d715 --- /dev/null +++ b/libraries/simulated/dev_pmod_i2s.vhd @@ -0,0 +1,327 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +library work; +use work.sim_utility.all; + + +entity pmod_i2s is + generic ( + -- Outgoing audio from FPGA to external sink + TX_IS_CLK: boolean := false; -- DAC is generating clocks + TX_MCLKX: positive := 256; -- MCLK/LRCLK + TX_RATE: positive := 44100; + TX_WIDTH: positive := 32; + + -- Incoming audio from external source to FPGA + RX_IS_CLK: boolean := false; -- ADC is generating clocks + RX_MCLKX: positive := 256; -- MCLK/LRCLK + RX_RATE: positive := 44100; + RX_WIDTH: positive := 32 + ); + port ( + J: inout std_logic_vector(7 downto 0); -- PMOD jumper + + file_read: in string(1 to 100); -- Assign to read WAV file and queue samples + is_empty: out boolean; -- Rx queue read from WAV file is empty + + file_write: in string(1 to 100) -- Assign to write queued samples to WAV file + ); +end pmod_i2s; + + +architecture behavioral of pmod_i2s is + + ---------------------------------------------------------------------------- + -- Tx + signal tx_mclk: std_logic; + signal tx_sck: std_logic; + signal tx_ws: std_logic; + signal tx_sd: std_logic; + + shared variable tx_queue: queue_t; + + ---------------------------------------------------------------------------- + -- Rx + signal rx_mclk: std_logic; + signal rx_sck: std_logic; + signal rx_ws: std_logic; + signal rx_sd: std_logic; + + shared variable rx_queue: queue_t; + +begin + + J <= (others => 'Z'); + ---------------------------------------------------------------------------- + -- Receive I2S from design and insert samples into Tx queue + + process + begin + tx_queue := queue_create; + wait; + end process; + + process (tx_sck, tx_ws, tx_sd) + variable tx_samp_l: std_logic_vector(TX_WIDTH-1 downto 0); + variable tx_samp_r: std_logic_vector(TX_WIDTH-1 downto 0); + begin + if rising_edge(tx_sck) then + if tx_ws = '0' then + tx_samp_l := tx_samp_l(TX_WIDTH-2 downto 0) & tx_sd; + else + tx_samp_r := tx_samp_r(TX_WIDTH-2 downto 0) & tx_sd; + end if; + end if; + if falling_edge(tx_ws) then + for i in 1 to TX_WIDTH/8 loop + queue_enq(tx_queue, tx_samp_l(7 downto 0)); + tx_samp_l := x"00" & tx_samp_l(TX_WIDTH-1 downto 8); + end loop; + for i in 1 to TX_WIDTH/8 loop + queue_enq(tx_queue, tx_samp_r(7 downto 0)); + tx_samp_r := x"00" & tx_samp_r(TX_WIDTH-1 downto 8); + end loop; + end if; + end process; + + process (file_write) + file f: bin_file_t; + variable fstatus: FILE_OPEN_STATUS; + variable value: std_logic_vector(7 downto 0); + variable len: integer; + begin + if not file_write'quiet then + len := queue_len(tx_queue); + + -- Open file + file_open(fstatus, f, file_write, WRITE_MODE); + assert fstatus = OPEN_OK report "Failed to open file '" & file_write & "'" severity failure; + + -- Write WAV file format headers + bwrite_str(f, "RIFF"); -- RIFF format specifier + bwrite_32le(f, 4+8+16+8+len); -- File length + bwrite_str(f, "WAVE"); -- WAV format specifier + + -- Format block + bwrite_str(f, "fmt "); -- Format block identifier + bwrite_32le(f, 16); -- Format block size + bwrite_16le(f, 1); -- Audio format + bwrite_16le(f, 2); -- Number of channels + bwrite_32le(f, TX_RATE); -- Sample rate + bwrite_32le(f, TX_RATE*2*TX_WIDTH/8); -- Bytes per second + bwrite_16le(f, 2*TX_WIDTH/8); -- Bytes per block + bwrite_16le(f, TX_WIDTH); -- Bits per sample + + -- Data block + bwrite_str(f, "data"); -- Data block specifier + bwrite_32le(f, len); -- Data block size + while queue_len(tx_queue) > 0 loop + queue_deq(tx_queue, value); + + -- Suppress warnings about metavalues by manually converting to zero + for i in value'range loop + if value(i) /= '0' and value(i) /= '1' then + value(i) := '0'; + end if; + end loop; + + bwrite_8(f, value); + end loop; + + -- Close file + file_close(f); + end if; + end process; + + + ---------------------------------------------------------------------------- + -- Remove samples from Rx queue and send to design over I2S + + process + begin + rx_queue := queue_create; + wait; + end process; + + process (rx_sck, rx_ws) + variable rx_samp_pair: std_logic_vector(RX_WIDTH*2-1 downto 0); + alias rx_samp_l: std_logic_vector(RX_WIDTH-1 downto 0) is rx_samp_pair(RX_WIDTH*2-1 downto RX_WIDTH); + alias rx_samp_r: std_logic_vector(RX_WIDTH-1 downto 0) is rx_samp_pair(RX_WIDTH-1 downto 0); + begin + if falling_edge(rx_ws) then + -- Load new sample from the queue + for i in 1 to RX_WIDTH/8 loop + rx_samp_l := x"00" & rx_samp_l(RX_WIDTH-1 downto 8); + queue_deq(rx_queue, rx_samp_l(RX_WIDTH-1 downto RX_WIDTH-8)); + end loop; + for i in 1 to RX_WIDTH/8 loop + rx_samp_r := x"00" & rx_samp_r(RX_WIDTH-1 downto 8); + queue_deq(rx_queue, rx_samp_r(RX_WIDTH-1 downto RX_WIDTH-8)); + end loop; + elsif falling_edge(rx_sck) then + -- Shift sample + rx_samp_pair := rx_samp_pair(RX_WIDTH*2-2 downto 0) & '0'; + end if; + + rx_sd <= rx_samp_pair(RX_WIDTH*2-1); + is_empty <= queue_len(rx_queue) > 0; + end process; + + process (file_read) + file f: bin_file_t; + variable fstatus: FILE_OPEN_STATUS; + + variable len_file: integer; + variable len_fmt: integer; + variable format: integer; + variable num_channels: integer; + variable file_rx_rate: integer; + variable bytes_per_sec: integer; + variable bytes_per_block: integer; + variable file_rx_width: integer; + variable len_data: integer; + + variable samp_pair: std_logic_vector(RX_WIDTH*2-1 downto 0); + alias samp_l: std_logic_vector(RX_WIDTH-1 downto 0) is samp_pair(RX_WIDTH*2-1 downto RX_WIDTH); + alias samp_r: std_logic_vector(RX_WIDTH-1 downto 0) is samp_pair(RX_WIDTH-1 downto 0); + variable c: character; + begin + if not file_read'quiet then + -- Open file + file_open(fstatus, f, file_read, READ_MODE); + assert fstatus = OPEN_OK report "Failed to open file '" & file_read & "'" severity failure; + + -- Parse WAV file header + bread_expect(f, "RIFF"); + bread_32le(f, len_file); + bread_expect(f, "WAVE"); + + -- Parse format header + bread_expect(f, "fmt "); + bread_32le(f, len_fmt); + bread_16le(f, format); + bread_16le(f, num_channels); + bread_32le(f, file_rx_rate); + bread_32le(f, bytes_per_sec); + bread_16le(f, bytes_per_block); + bread_16le(f, file_rx_width); + + -- Double check format for sanity + assert len_fmt = 16 severity failure; + assert format = 1 severity failure; + assert num_channels = 2 severity failure; + assert file_rx_rate = RX_RATE report "Input file sample rate mismatch" severity warning; + + -- Parse data block + bread_expect(f, "data"); + bread_32le(f, len_data); + for i in 1 to len_data/bytes_per_block loop + -- Samples need to be read and queued separately because + -- the file and interface may have different sample widths + + -- Read left sample + samp_l := (others => '0'); + for i in 1 to file_rx_width/8 loop + samp_l := x"00" & samp_l(RX_WIDTH-1 downto 8); + bread_8(f, samp_l(RX_WIDTH-1 downto RX_WIDTH-8)); + end loop; + + -- Read right sample + samp_r := (others => '0'); + for i in 1 to file_rx_width/8 loop + samp_r := x"00" & samp_r(RX_WIDTH-1 downto 8); + bread_8(f, samp_r(RX_WIDTH-1 downto RX_WIDTH-8)); + end loop; + + -- Dump sample into queue + for i in 1 to RX_WIDTH*2/8 loop + queue_enq(rx_queue, samp_pair(7 downto 0)); + samp_pair := x"00" & samp_pair(RX_WIDTH*2-1 downto 8); + end loop; + end loop; + end if; + end process; + + + ---------------------------------------------------------------------------- + -- Tx clock generation and connector mapping + + g_tx_mclk: if TX_IS_CLK generate + p_tx_ws: process + begin + tx_ws <= '0'; + wait for 500 ms / TX_RATE; + tx_ws <= '1'; + wait for 500 ms / TX_RATE; + end process; + + p_tx_sck: process + begin + tx_sck <= '0'; + wait for 500 ms / (TX_RATE * TX_WIDTH * 2); + tx_sck <= '1'; + wait for 500 ms / (TX_RATE * TX_WIDTH * 2); + end process; + + p_tx_mclk: process + begin + tx_mclk <= '0'; + wait for 500 ms / (TX_RATE * TX_MCLKX); + tx_mclk <= '1'; + wait for 500 ms / (TX_RATE * TX_MCLKX); + end process; + + J(0) <= tx_mclk; + J(2) <= tx_sck; + J(1) <= tx_ws; + end generate; + g_tx_n_mclk: if not TX_IS_CLK generate + tx_mclk <= J(0); + tx_sck <= J(2); + tx_ws <= J(1); + end generate; + tx_sd <= J(3); + + + ---------------------------------------------------------------------------- + -- Rx clock generation and connector mapping + + g_rx_mclk: if RX_IS_CLK generate + p_rx_ws: process + begin + rx_ws <= '0'; + wait for 500 ms / RX_RATE; + rx_ws <= '1'; + wait for 500 ms / RX_RATE; + end process; + + p_rx_sck: process + begin + rx_sck <= '0'; + wait for 500 ms / (RX_RATE * RX_WIDTH * 2); + rx_sck <= '1'; + wait for 500 ms / (RX_RATE * RX_WIDTH * 2); + end process; + + p_rx_mclk: process + begin + rx_mclk <= '0'; + wait for 500 ms / (RX_RATE * RX_MCLKX); + rx_mclk <= '1'; + wait for 500 ms / (RX_RATE * RX_MCLKX); + end process; + + J(4) <= rx_mclk; + J(6) <= rx_sck; + J(5) <= rx_ws; + end generate; + g_rx_n_mclk: if not RX_IS_CLK generate + rx_mclk <= J(4); + rx_sck <= J(6); + rx_ws <= J(5); + end generate; + J(7) <= rx_sd; + +end behavioral; diff --git a/libraries/simulated/tests/test_dev_pmod_i2s.vhd b/libraries/simulated/tests/test_dev_pmod_i2s.vhd new file mode 100644 index 0000000..29039da --- /dev/null +++ b/libraries/simulated/tests/test_dev_pmod_i2s.vhd @@ -0,0 +1,42 @@ +library ieee; +use ieee.std_logic_1164.all; + +library work; +use work.sim_utility.all; + + +entity test_dev_pmod_i2s is +end test_dev_pmod_i2s; + + +architecture behavior of test_dev_pmod_i2s is + + signal J: std_logic_vector(7 downto 0) := (others => '0'); + + signal file_read: string(1 to 100); + signal file_write: string(1 to 100); + +begin + + p_test: process + begin + file_read <= str_pad("/home/ryan/Projects/VHDL/mp3tape/test_in.wav", 100); + wait for 1 ms; + file_write <= str_pad("test_out.wav", 100); + wait; + end process; + J(3) <= J(7); + + + e_uut: entity work.pmod_i2s + generic map ( + TX_IS_CLK => true, + RX_IS_CLK => true + ) + port map ( + J => J, + file_read => file_read, + file_write => file_write + ); + +end; diff --git a/libraries/simulated/util_pkg.vhd b/libraries/simulated/util_pkg.vhd index dc8a08d..51d09fb 100644 --- a/libraries/simulated/util_pkg.vhd +++ b/libraries/simulated/util_pkg.vhd @@ -9,8 +9,29 @@ package sim_utility is -- String manipulation function str_endswith(str: string; suffix: string) return boolean; + function str_pad(str: string; len: integer) return string; + ---------------------------------------------------------------------------- + -- Binary file IO + + type bin_file_t is file of character; + procedure bwrite_str(file f: bin_file_t; value: in string); + procedure bwrite_8(file f: bin_file_t; value: in std_logic_vector(7 downto 0)); + procedure bwrite_8(file f: bin_file_t; value: in integer); + procedure bwrite_16le(file f: bin_file_t; value: in std_logic_vector(15 downto 0)); + procedure bwrite_16le(file f: bin_file_t; value: in integer); + procedure bwrite_32le(file f: bin_file_t; value: in std_logic_vector(31 downto 0)); + procedure bwrite_32le(file f: bin_file_t; value: in integer); + + procedure bread_expect(file f: bin_file_t; value: in string); + procedure bread_8(file f: bin_file_t; value: out std_logic_vector(7 downto 0)); + procedure bread_8(file f: bin_file_t; value: out integer); + procedure bread_16le(file f: bin_file_t; value: out std_logic_vector(15 downto 0)); + procedure bread_16le(file f: bin_file_t; value: out integer); + procedure bread_32le(file f: bin_file_t; value: out std_logic_vector(31 downto 0)); + procedure bread_32le(file f: bin_file_t; value: out integer); + ---------------------------------------------------------------------------- -- Sparse byte array for simulated memory devices -- @@ -102,6 +123,127 @@ package body sim_utility is end if; end function; + function str_pad(str: string; len: integer) return string is + variable s: string(1 to len); + begin + for i in 1 to len loop + if i <= str'high then + s(i) := str(i); + else + s(i) := NUL; + end if; + end loop; + return s; + end function; + + + ---------------------------------------------------------------------------- + -- Binary file IO + + procedure bwrite_str(file f: bin_file_t; value: in string) is + begin + for i in value'range loop + write(f, value(i)); + end loop; + end procedure; + + procedure bwrite_8(file f: bin_file_t; value: in std_logic_vector(7 downto 0)) is + begin + write(f, character'val(to_integer(unsigned(value)))); + end procedure; + + procedure bwrite_8(file f: bin_file_t; value: in integer) is + begin + write(f, character'val(value)); + end procedure; + + procedure bwrite_16le(file f: bin_file_t; value: in std_logic_vector(15 downto 0)) is + begin + write(f, character'val(to_integer(unsigned(value( 7 downto 0))))); + write(f, character'val(to_integer(unsigned(value(15 downto 8))))); + end procedure; + + procedure bwrite_16le(file f: bin_file_t; value: in integer) is + variable slv: std_logic_vector(15 downto 0); + begin + if value > 0 then + slv := std_logic_vector(to_unsigned(value, 16)); + else + slv := std_logic_vector(to_signed(value, 16)); + end if; + bwrite_16le(f, slv); + end procedure; + + procedure bwrite_32le(file f: bin_file_t; value: in std_logic_vector(31 downto 0)) is + begin + write(f, character'val(to_integer(unsigned(value( 7 downto 0))))); + write(f, character'val(to_integer(unsigned(value(15 downto 8))))); + write(f, character'val(to_integer(unsigned(value(23 downto 16))))); + write(f, character'val(to_integer(unsigned(value(31 downto 24))))); + end procedure; + + procedure bwrite_32le(file f: bin_file_t; value: in integer) is + variable slv: std_logic_vector(31 downto 0); + begin + if value > 0 then + slv := std_logic_vector(to_unsigned(value, 32)); + else + slv := std_logic_vector(to_signed(value, 32)); + end if; + bwrite_32le(f, slv); + end procedure; + + procedure bread_expect(file f: bin_file_t; value: in string) is + variable c: character; + begin + for i in value'range loop + read(f, c); + assert c = value(i) severity failure; + end loop; + end procedure; + + procedure bread_8(file f: bin_file_t; value: out std_logic_vector(7 downto 0)) is + variable v: integer; + begin + bread_8(f, v); + value := std_logic_vector(to_unsigned(v, 8)); + end procedure; + + procedure bread_8(file f: bin_file_t; value: out integer) is + variable c: character; + begin + read(f, c); + value := character'pos(c); + end procedure; + + procedure bread_16le(file f: bin_file_t; value: out std_logic_vector(15 downto 0)) is + begin + bread_8(f, value( 7 downto 0)); + bread_8(f, value(15 downto 8)); + end procedure; + + procedure bread_16le(file f: bin_file_t; value: out integer) is + variable slv: std_logic_vector(15 downto 0); + begin + bread_16le(f, slv); + value := to_integer(unsigned(slv)); + end procedure; + + procedure bread_32le(file f: bin_file_t; value: out std_logic_vector(31 downto 0)) is + begin + bread_8(f, value( 7 downto 0)); + bread_8(f, value(15 downto 8)); + bread_8(f, value(23 downto 16)); + bread_8(f, value(31 downto 24)); + end procedure; + + procedure bread_32le(file f: bin_file_t; value: out integer) is + variable slv: std_logic_vector(31 downto 0); + begin + bread_32le(f, slv); + value := to_integer(unsigned(slv)); + end procedure; + ---------------------------------------------------------------------------- -- Sparse byte array for simulated memory devices