]> git.the-white-hart.net Git - vhdl/commitdiff
Add WAV to/from I2S simulated PMOD device
authorrs <>
Mon, 12 Jan 2026 19:22:14 +0000 (13:22 -0600)
committerrs <>
Mon, 12 Jan 2026 19:22:14 +0000 (13:22 -0600)
libraries/simulated/dev_pmod_i2s.vhd [new file with mode: 0644]
libraries/simulated/tests/test_dev_pmod_i2s.vhd [new file with mode: 0644]
libraries/simulated/util_pkg.vhd

diff --git a/libraries/simulated/dev_pmod_i2s.vhd b/libraries/simulated/dev_pmod_i2s.vhd
new file mode 100644 (file)
index 0000000..6e5d715
--- /dev/null
@@ -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 (file)
index 0000000..29039da
--- /dev/null
@@ -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;
index dc8a08d41be3b1b88f069e57b45ba20afed4591e..51d09fb52750cea1237322ccaa937878e47c836b 100644 (file)
@@ -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