]> git.the-white-hart.net Git - vhdl/commitdiff
Update I2S-WAV entities and related packages
authorrs <>
Thu, 15 Jan 2026 00:00:46 +0000 (18:00 -0600)
committerrs <>
Thu, 15 Jan 2026 00:00:46 +0000 (18:00 -0600)
libraries/simulated/dev_pmod_i2s.vhd
libraries/simulated/pkg_binfile.vhd [new file with mode: 0644]
libraries/simulated/pkg_intqueue.vhd
libraries/simulated/proto_i2s_ctrl.vhd [new file with mode: 0644]
libraries/simulated/proto_i2s_from_wav.vhd [new file with mode: 0644]
libraries/simulated/proto_i2s_to_wav.vhd [new file with mode: 0644]
libraries/simulated/tests/test_dev_pmod_i2s.vhd
libraries/simulated/tests/test_i2s_to_wav.vhd [new file with mode: 0644]
libraries/simulated/tests/test_intqueue.vhd

index 9c47fa3c5ec678b754cfdbfc2541d6c35270e66c..24c63575e199439af804da25ba46f4bb11b782b1 100644 (file)
@@ -1,13 +1,3 @@
---------------------------------------------------------------------------------
--- This is not its final form
---
--- * Separate I2S->WAV and WAV->I2S into separate entities
--- * Queues should carry integers, not bytes
--- * Queues should allocate blocks rather than individual items (it's a bit slow)
--- * Floating point error accumulation causes MCLK drift
--- * More sanity checks on file format (ensure sample width divisible by 8)
---------------------------------------------------------------------------------
-
 library ieee;
 use ieee.std_logic_1164.all;
 use ieee.numeric_std.all;
@@ -16,7 +6,7 @@ library work;
 use work.sim_utility.all;
 
 
-entity pmod_i2s is
+entity dev_pmod_i2s is
        generic (
                -- Outgoing audio from FPGA to external sink
                TX_IS_CLK: boolean  := false;  -- DAC is generating clocks
@@ -38,10 +28,10 @@ entity pmod_i2s is
 
                file_write: in    string(1 to 100)   -- Assign to write queued samples to WAV file
        );
-end pmod_i2s;
+end dev_pmod_i2s;
 
 
-architecture behavioral of pmod_i2s is
+architecture behavioral of dev_pmod_i2s is
 
        ----------------------------------------------------------------------------
        -- Tx
@@ -64,224 +54,54 @@ architecture behavioral of pmod_i2s is
 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;
+       -- Tx
 
-                       -- 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
+       e_tx: entity work.proto_i2s_to_wav
+               generic map (
+                       SAMPLE_RATE  => TX_RATE,
+                       SAMPLE_WIDTH => TX_WIDTH
+               )
+               port map (
+                       sck      => tx_sck,
+                       ws       => tx_ws,
+                       sd       => tx_sd,
+                       filename => file_write
+               );
 
-                               -- 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;
+       ----------------------------------------------------------------------------
+       -- Rx
 
-                               -- 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;
+       e_rx: entity work.proto_i2s_from_wav
+               generic map (
+                       SAMPLE_RATE => RX_RATE
+               )
+               port map (
+                       sck      => rx_sck,
+                       ws       => rx_ws,
+                       sd       => rx_sd,
+                       filename => file_read,
+                       is_empty => is_empty
+               );
 
 
        ----------------------------------------------------------------------------
        -- 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;
+               e_tx_clk: entity work.proto_i2s_ctrl
+                       generic map (
+                               MCLKX => TX_MCLKX,
+                               WIDTH => TX_WIDTH,
+                               RATE  => TX_RATE
+                       )
+                       port map (
+                               mclk => tx_mclk,
+                               sck  => tx_sck,
+                               ws   => tx_ws
+                       );
 
                J(0) <= tx_mclk;
                J(2) <= tx_sck;
@@ -299,29 +119,17 @@ begin
        -- 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;
+               e_rx_clk: entity work.proto_i2s_ctrl
+                       generic map (
+                               MCLKX => RX_MCLKX,
+                               WIDTH => RX_WIDTH,
+                               RATE  => RX_RATE
+                       )
+                       port map (
+                               mclk => rx_mclk,
+                               sck  => rx_sck,
+                               ws   => rx_ws
+                       );
 
                J(4) <= rx_mclk;
                J(6) <= rx_sck;
diff --git a/libraries/simulated/pkg_binfile.vhd b/libraries/simulated/pkg_binfile.vhd
new file mode 100644 (file)
index 0000000..b24c5f3
--- /dev/null
@@ -0,0 +1,343 @@
+library ieee;
+use ieee.std_logic_1164.all;
+use ieee.numeric_std.all;
+
+
+package pkg_binfile is
+
+       ----------------------------------------------------------------------------
+       -- Generic binary file IO
+
+       type bin_file_t is file of character;
+       subtype u8  is integer range 0 to 255;
+       subtype s8  is integer range -128 to 127;
+       subtype u16 is integer range 0 to 65535;
+       subtype s16 is integer range -32768 to 32767;
+       subtype u32 is positive;  -- Best I can do pre-VHDL-2019
+       subtype s32 is integer;   -- Should update with range if using VHDL-2019
+
+       procedure write_str(file f: bin_file_t; value: in string);
+       procedure write_v8(file f: bin_file_t; value: in std_logic_vector(7 downto 0));
+       procedure write_s8(file f: bin_file_t; value: in s8);
+       procedure write_u8(file f: bin_file_t; value: in u8);
+       procedure write_v16_le(file f: bin_file_t; value: in std_logic_vector(15 downto 0));
+       procedure write_s16_le(file f: bin_file_t; value: in s16);
+       procedure write_u16_le(file f: bin_file_t; value: in u16);
+       procedure write_v32_le(file f: bin_file_t; value: in std_logic_vector(31 downto 0));
+       procedure write_s32_le(file f: bin_file_t; value: in s32);
+       procedure write_u32_le(file f: bin_file_t; value: in u32);
+
+       procedure read_expect(file f: bin_file_t; value: in string);
+       procedure read_v8(file f: bin_file_t; value: out std_logic_vector(7 downto 0));
+       procedure read_s8(file f: bin_file_t; value: out s8);
+       procedure read_u8(file f: bin_file_t; value: out u8);
+       procedure read_v16_le(file f: bin_file_t; value: out std_logic_vector(15 downto 0));
+       procedure read_s16_le(file f: bin_file_t; value: out s16);
+       procedure read_u16_le(file f: bin_file_t; value: out u16);
+       procedure read_v32_le(file f: bin_file_t; value: out std_logic_vector(31 downto 0));
+       procedure read_s32_le(file f: bin_file_t; value: out s32);
+       procedure read_u32_le(file f: bin_file_t; value: out u32);
+
+
+       ----------------------------------------------------------------------------
+       -- WAV audio file
+
+       type wav_header_t is record
+               format:       natural;
+               num_channels: natural;
+               sample_rate:  natural;
+               sample_width: positive;
+               num_samples:  natural;
+       end record;
+
+       procedure write_wav_header(file f: bin_file_t; value: in wav_header_t);
+
+       -- Samples are adjusted such that MSB is preserved and LSBs may be truncated
+       procedure write_wav_pair(file f: bin_file_t; format: in wav_header_t; left: in s32; right: in s32);
+
+       procedure read_wav_header(file f: bin_file_t; value: out wav_header_t);
+
+       -- Samples are adjusted such that MSB of sample is MSB of output integer
+       procedure read_wav_pair(file f: bin_file_t; format: in wav_header_t; left: out s32; right: out s32);
+
+
+       ----------------------------------------------------------------------------
+       -- BMP image file (TODO)
+
+
+       ----------------------------------------------------------------------------
+       -- Intel HEX memory file (TODO)
+
+end pkg_binfile;
+
+
+package body pkg_binfile is
+
+       procedure write_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 write_v8(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 write_s8(file f: bin_file_t; value: in s8) is
+       begin
+               write_v8(f, std_logic_vector(to_signed(value, 8)));
+       end procedure;
+
+
+       procedure write_u8(file f: bin_file_t; value: in u8) is
+       begin
+               write(f, character'val(value));
+       end procedure;
+
+
+       procedure write_v16_le(file f: bin_file_t; value: in std_logic_vector(15 downto 0)) is
+       begin
+               write_v8(f, value( 7 downto 0));
+               write_v8(f, value(15 downto 8));
+       end procedure;
+
+
+       procedure write_s16_le(file f: bin_file_t; value: in s16) is
+       begin
+               write_v16_le(f, std_logic_vector(to_signed(value, 16)));
+       end procedure;
+
+
+       procedure write_u16_le(file f: bin_file_t; value: in u16) is
+       begin
+               write_v16_le(f, std_logic_vector(to_unsigned(value, 16)));
+       end procedure;
+
+
+       procedure write_v32_le(file f: bin_file_t; value: in std_logic_vector(31 downto 0)) is
+       begin
+               write_v8(f, value( 7 downto  0));
+               write_v8(f, value(15 downto  8));
+               write_v8(f, value(23 downto 16));
+               write_v8(f, value(31 downto 24));
+       end procedure;
+
+
+       procedure write_s32_le(file f: bin_file_t; value: in s32) is
+       begin
+               write_v32_le(f, std_logic_vector(to_signed(value, 32)));
+       end procedure;
+
+
+       procedure write_u32_le(file f: bin_file_t; value: in u32) is
+       begin
+               write_v32_le(f, std_logic_vector(to_unsigned(value, 32)));
+       end procedure;
+
+
+       procedure read_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 read_v8(file f: bin_file_t; value: out std_logic_vector(7 downto 0)) is
+               variable v: u8;
+       begin
+               read_u8(f, v);
+               value := std_logic_vector(to_unsigned(v, 8));
+       end procedure;
+
+
+       procedure read_s8(file f: bin_file_t; value: out s8) is
+               variable v: std_logic_vector(7 downto 0);
+       begin
+               read_v8(f, v);
+               value := to_integer(signed(v));
+       end procedure;
+
+
+       procedure read_u8(file f: bin_file_t; value: out u8) is
+               variable c: character;
+       begin
+               read(f, c);
+               value := character'pos(c);
+       end procedure;
+
+
+       procedure read_v16_le(file f: bin_file_t; value: out std_logic_vector(15 downto 0)) is
+       begin
+               read_v8(f, value( 7 downto 0));
+               read_v8(f, value(15 downto 8));
+       end procedure;
+
+
+       procedure read_s16_le(file f: bin_file_t; value: out s16) is
+               variable v: std_logic_vector(15 downto 0);
+       begin
+               read_v16_le(f, v);
+               value := to_integer(signed(v));
+       end procedure;
+
+
+       procedure read_u16_le(file f: bin_file_t; value: out u16) is
+               variable v: std_logic_vector(15 downto 0);
+       begin
+               read_v16_le(f, v);
+               value := to_integer(unsigned(v));
+       end procedure;
+
+
+       procedure read_v32_le(file f: bin_file_t; value: out std_logic_vector(31 downto 0)) is
+       begin
+               read_v8(f, value( 7 downto  0));
+               read_v8(f, value(15 downto  8));
+               read_v8(f, value(23 downto 16));
+               read_v8(f, value(31 downto 24));
+       end procedure;
+
+
+       procedure read_s32_le(file f: bin_file_t; value: out s32) is
+               variable v: std_logic_vector(31 downto 0);
+       begin
+               read_v32_le(f, v);
+               value := to_integer(signed(v));
+       end procedure;
+
+
+       procedure read_u32_le(file f: bin_file_t; value: out u32) is
+               variable v: std_logic_vector(31 downto 0);
+       begin
+               read_v32_le(f, v);
+               value := to_integer(unsigned(v));
+       end procedure;
+
+
+       procedure write_wav_header(file f: bin_file_t; value: in wav_header_t) is
+               variable bytes_per_second: u32;
+               variable bytes_per_block:  u32;
+               variable data_length:      u32;
+       begin
+               bytes_per_block  := value.num_channels * value.sample_width / 8;
+               bytes_per_second := value.sample_rate * bytes_per_block;
+               data_length      := value.num_samples * bytes_per_block;
+
+               assert value.format = 1 report "Unsupported sample format" severity warning;
+               assert value.sample_width mod 8 = 0 report "Sample width not a multiple of 8 bits" severity error;
+
+               -- RIFF header and WAV format indicator
+               write_str(f, "RIFF");
+               write_u32_le(f, 4+8+16+8+data_length);  -- Total file size
+               write_str(f, "WAVE");
+
+               -- Format block
+               write_str(f, "fmt ");                   -- Format block identifier
+               write_u32_le(f, 16);                    -- Format block size
+               write_u16_le(f, value.format);          -- Audio format
+               write_u16_le(f, value.num_channels);    -- Number of channels
+               write_u32_le(f, value.sample_rate);     -- Sample rate
+               write_u32_le(f, bytes_per_second);      -- Bytes per second
+               write_u16_le(f, bytes_per_block);       -- Bytes per block
+               write_u16_le(f, value.sample_width);    -- Bits per sample
+
+               -- Data block header
+               write_str(f, "data");                   -- Data block specifier
+               write_u32_le(f, data_length);           -- Data block size
+       end procedure;
+
+
+       procedure write_wav_pair(file f: bin_file_t; format: in wav_header_t; left: in s32; right: in s32) is
+       begin
+               assert format.format = 1 report "Unexpected sample format" severity warning;
+               assert format.num_channels = 2 report "Unexpected number of channels" severity warning;
+               if format.sample_width = 32 then
+                       write_s32_le(f, left);
+                       write_s32_le(f, right);
+               elsif format.sample_width = 16 then
+                       write_s16_le(f, left / 65536);
+                       write_s16_le(f, right / 65536);
+               else
+                       assert false report "Unexpected sample width" severity error;
+               end if;
+       end procedure;
+
+
+       procedure read_wav_header(file f: bin_file_t; value: out wav_header_t) is
+               variable len_file:         u32;
+               variable len_fmt_header:   u16;
+               variable format:           u16;
+               variable num_channels:     u16;
+               variable sample_rate:      u32;
+               variable bytes_per_second: u32;
+               variable bytes_per_block:  u16;
+               variable sample_width:     u16;
+               variable len_data:         u32;
+       begin
+               -- RIFF header and WAV format indicator
+               read_expect(f, "RIFF");
+               read_u32_le(f, len_file);
+               read_expect(f, "WAVE");
+
+               -- Format block
+               read_expect(f, "fmt ");
+               read_u32_le(f, len_fmt_header);
+               read_u16_le(f, format);
+               read_u16_le(f, num_channels);
+               read_u32_le(f, sample_rate);
+               read_u32_le(f, bytes_per_second);
+               read_u16_le(f, bytes_per_block);
+               read_u16_le(f, sample_width);
+
+               -- Data block header
+               read_expect(f, "data");
+               read_u32_le(f, len_data);
+
+               -- Consistency checks
+
+               -- Metadata after the data section can cause the file length to be longer than data length would suggest
+               -- Make sure the reported file length is no shorter than the data though
+               assert len_file >= 4+8+16+8+len_data report "Data length and file length fields inconsistent" severity error;
+               assert len_fmt_header = 16 report "WAV fmt header has unexpected length" severity error;
+               assert format = 1 report "Unsupported sample format" severity warning;
+               assert sample_width mod 8 = 0 report "Sample width not a multiple of 8 bits" severity error;
+               assert bytes_per_block = sample_width * num_channels / 8 report "Inconsistent bytes per block" severity error;
+               assert bytes_per_second = bytes_per_block * sample_rate report "Inconsistent bytes per second" severity error;
+               assert len_data mod bytes_per_block = 0 report "Non-integer number of samples" severity error;
+
+               -- Store results
+               value.format       := format;
+               value.num_channels := num_channels;
+               value.sample_rate  := sample_rate;
+               value.sample_width := sample_width;
+               value.num_samples  := len_data / bytes_per_block;
+       end procedure;
+
+
+       procedure read_wav_pair(file f: bin_file_t; format: in wav_header_t; left: out s32; right: out s32) is
+               variable sl: s32;
+               variable sr: s32;
+       begin
+               assert format.format = 1 report "Unexpected sample format" severity warning;
+               assert format.num_channels = 2 report "Unexpected number of channels" severity warning;
+               if format.sample_width = 32 then
+                       read_s32_le(f, left);
+                       read_s32_le(f, right);
+               elsif format.sample_width = 16 then
+                       read_s16_le(f, sl);
+                       read_s16_le(f, sr);
+                       left  := sl * 65536;
+                       right := sr * 65536;
+               else
+                       assert false report "Unexpected sample width" severity error;
+               end if;
+       end procedure;
+
+end pkg_binfile;
index 83cbc8258ca8820246d8320fe4675c5aca10e310..4aed23894313877d4ee0b24df03f18962e3d8ab4 100644 (file)
@@ -1,8 +1,3 @@
-library ieee;
-use ieee.std_logic_1164.all;
-use ieee.numeric_std.all;
-
-
 package pkg_intqueue is
 
        -- Internal types (do not use)
diff --git a/libraries/simulated/proto_i2s_ctrl.vhd b/libraries/simulated/proto_i2s_ctrl.vhd
new file mode 100644 (file)
index 0000000..2dea600
--- /dev/null
@@ -0,0 +1,52 @@
+library ieee;
+use ieee.std_logic_1164.all;
+
+
+entity proto_i2s_ctrl is
+       generic (
+               MCLKX: positive := 256;
+               WIDTH: positive := 32;
+               RATE:  positive := 44100
+       );
+       port (
+               mclk: out std_logic;
+               sck:  out std_logic;
+               ws:   out std_logic
+       );
+end proto_i2s_ctrl;
+
+
+architecture behavioral of proto_i2s_ctrl is
+
+       constant MCLK_HALF_PERIOD: time := (500 ms / RATE) / MCLKX;
+
+       constant MCLKS_PER_WS:  positive := MCLKX;
+       constant MCLKS_PER_SCK: positive := MCLKS_PER_WS / (2 * WIDTH);
+
+begin
+
+       p_mclk: process
+       begin
+               mclk <= '0';
+               wait for MCLK_HALF_PERIOD;
+               mclk <= '1';
+               wait for MCLK_HALF_PERIOD;
+       end process;
+
+       p_sck: process
+       begin
+               sck <= '0';
+               wait for MCLK_HALF_PERIOD * MCLKS_PER_SCK;
+               sck <= '1';
+               wait for MCLK_HALF_PERIOD * MCLKS_PER_SCK;
+       end process;
+
+       p_ws: process
+       begin
+               ws <= '0';
+               wait for MCLK_HALF_PERIOD * MCLKS_PER_WS;
+               ws <= '1';
+               wait for MCLK_HALF_PERIOD * MCLKS_PER_WS;
+       end process;
+
+end behavioral;
diff --git a/libraries/simulated/proto_i2s_from_wav.vhd b/libraries/simulated/proto_i2s_from_wav.vhd
new file mode 100644 (file)
index 0000000..67e31ef
--- /dev/null
@@ -0,0 +1,102 @@
+library ieee;
+use ieee.std_logic_1164.all;
+use ieee.numeric_std.all;
+
+library work;
+use work.pkg_binfile.all;
+use work.pkg_intqueue.all;
+
+
+entity proto_i2s_from_wav is
+       generic (
+               SAMPLE_RATE: positive := 44100
+       );
+       port (
+               sck:      in  std_logic;
+               ws:       in  std_logic;
+               sd:       out std_logic;
+
+               filename: in  string(1 to 100);
+               is_empty: out boolean
+       );
+end proto_i2s_from_wav;
+
+
+architecture behavioral of proto_i2s_from_wav is
+
+       shared variable q: intqueue_t;
+
+       signal load_trigger: boolean;
+       signal ws0_reg:      std_logic;
+       signal ws1_reg:      std_logic;
+
+begin
+
+       process (filename)
+               file     f:       bin_file_t;
+               variable fstatus: FILE_OPEN_STATUS;
+               variable header:  wav_header_t;
+
+               variable samp_l:  integer;
+               variable samp_r:  integer;
+       begin
+               if filename'quiet then  -- Initial invocation after simulation begins
+                       q := intqueue_create;
+               end if;
+
+               file_open(fstatus, f, filename, READ_MODE);
+               if fstatus = OPEN_OK then
+                       read_wav_header(f, header);
+                       assert header.sample_rate = SAMPLE_RATE report "Sample rate of input file does not match simulated design" severity warning;
+                       for i in 1 to header.num_samples loop
+                               read_wav_pair(f, header, samp_l, samp_r);
+                               intqueue_enq(q, samp_l);
+                               intqueue_enq(q, samp_r);
+                       end loop;
+               end if;
+               file_close(f);
+
+               load_trigger <= true;
+       end process;
+
+
+       process (sck, ws, ws0_reg)
+       begin
+               if rising_edge(sck) then
+                       ws1_reg <= ws0_reg;
+                       ws0_reg <= ws;
+               end if;
+       end process;
+
+
+       process (load_trigger, sck, ws0_reg, ws1_reg)
+               variable samp_l:     integer;
+               variable samp_r:     integer;
+               variable samp_l_slv: std_logic_vector(31 downto 0);
+               variable samp_r_slv: std_logic_vector(31 downto 0);
+       begin
+               if falling_edge(sck) then
+                       -- Retrieve new samples when switching back to left channel
+                       if ws0_reg = '0' and ws1_reg = '1' then
+                               intqueue_deq(q, samp_l);
+                               intqueue_deq(q, samp_r);
+                               samp_l_slv := std_logic_vector(to_signed(samp_l, 32));
+                               samp_r_slv := std_logic_vector(to_signed(samp_r, 32));
+                       end if;
+
+                       -- Output the MSB of the current channel and shift the sample
+                       if ws0_reg = '0' then
+                               sd <= samp_l_slv(31);
+                               samp_l_slv := samp_l_slv(30 downto 0) & '0';
+                       else
+                               sd <= samp_r_slv(31);
+                               samp_r_slv := samp_r_slv(30 downto 0) & '0';
+                       end if;
+               end if;
+
+               -- Sensitivity on load_trigger lets us recompute is_empty after loading a WAV file
+               -- without having to wait for I2S clocks
+               is_empty <= intqueue_len(q) = 0;
+       end process;
+
+end behavioral;
diff --git a/libraries/simulated/proto_i2s_to_wav.vhd b/libraries/simulated/proto_i2s_to_wav.vhd
new file mode 100644 (file)
index 0000000..c310723
--- /dev/null
@@ -0,0 +1,101 @@
+library ieee;
+use ieee.std_logic_1164.all;
+use ieee.numeric_std.all;
+
+library work;
+use work.pkg_binfile.all;
+use work.pkg_intqueue.all;
+
+
+entity proto_i2s_to_wav is
+       generic (
+               SAMPLE_RATE:  positive := 44100;
+               SAMPLE_WIDTH: positive := 16
+       );
+       port (
+               sck:      in  std_logic;
+               ws:       in  std_logic;
+               sd:       in  std_logic;
+
+               filename: in  string(1 to 100)
+       );
+end proto_i2s_to_wav;
+
+
+architecture behavioral of proto_i2s_to_wav is
+
+       shared variable q: intqueue_t;
+
+       signal ws0_reg: std_logic;
+       signal ws1_reg: std_logic;
+
+begin
+
+       process (filename)
+               file     f:       bin_file_t;
+               variable fstatus: FILE_OPEN_STATUS;
+               variable header:  wav_header_t;
+
+               variable samp_l:  integer;
+               variable samp_r:  integer;
+       begin
+               if filename'quiet then
+                       q := intqueue_create;
+               else
+                       file_open(fstatus, f, filename, WRITE_MODE);
+                       if fstatus = OPEN_OK then
+                               header.format       := 1;
+                               header.num_channels := 2;
+                               header.sample_rate  := SAMPLE_RATE;
+                               header.sample_width := SAMPLE_WIDTH;
+                               header.num_samples  := intqueue_len(q) / 2;
+                               write_wav_header(f, header);
+
+                               for i in 1 to header.num_samples loop
+                                       intqueue_deq(q, samp_l);
+                                       intqueue_deq(q, samp_r);
+                                       write_wav_pair(f, header, samp_l, samp_r);
+                               end loop;
+                       end if;
+                       file_close(f);
+               end if;
+       end process;
+
+
+       process (sck, ws, ws0_reg)
+       begin
+               if rising_edge(sck) then
+                       ws1_reg <= ws0_reg;
+                       ws0_reg <= ws;
+               end if;
+       end process;
+
+
+       process (sck, ws0_reg, ws1_reg, sd)
+               variable i:      integer := 0;
+               variable samp_l: std_logic_vector(31 downto 0) := (others => '0');
+               variable samp_r: std_logic_vector(31 downto 0) := (others => '0');
+       begin
+               if rising_edge(sck) then
+                       -- Insert current samples into queue when switching back to left channel
+                       if ws0_reg = '0' and ws1_reg = '1' then
+                               intqueue_enq(q, to_integer(signed(samp_l)));
+                               intqueue_enq(q, to_integer(signed(samp_r)));
+                       end if;
+
+                       -- Reset bit index when switching between samples
+                       if ws0_reg /= ws1_reg then
+                               i := 31;
+                       end if;
+
+                       -- Insert data bit into sample and adjust index
+                       if ws0_reg = '0' then
+                               samp_l(i) := sd;
+                       else
+                               samp_r(i) := sd;
+                       end if;
+                       i := i - 1;
+               end if;
+       end process;
+
+end behavioral;
index 29039dae7529def315228d2823914def3c5f6731..ea82c261ffdd812ba2b6e3e80b9da42b7aaf061b 100644 (file)
@@ -20,15 +20,15 @@ begin
 
        p_test: process
        begin
-               file_read <= str_pad("/home/ryan/Projects/VHDL/mp3tape/test_in.wav", 100);
-               wait for 1 ms;
+               file_read <= str_pad("test_in.wav", 100);
+               wait for 10 ms;
                file_write <= str_pad("test_out.wav", 100);
                wait;
        end process;
        J(3) <= J(7);
 
 
-       e_uut: entity work.pmod_i2s
+       e_uut: entity work.dev_pmod_i2s
                generic map (
                        TX_IS_CLK => true,
                        RX_IS_CLK => true
diff --git a/libraries/simulated/tests/test_i2s_to_wav.vhd b/libraries/simulated/tests/test_i2s_to_wav.vhd
new file mode 100644 (file)
index 0000000..5174ad8
--- /dev/null
@@ -0,0 +1,70 @@
+library ieee;
+use ieee.std_logic_1164.all;
+
+library work;
+use work.sim_utility.all;
+
+
+entity test_i2s_to_wav is
+end test_i2s_to_wav;
+
+
+architecture behavior of test_i2s_to_wav is
+
+       signal mclk: std_logic;
+       signal sck:  std_logic;
+       signal ws:   std_logic;
+       signal sd:   std_logic;
+
+       signal is_empty:       boolean;
+       signal load_filename:  string(1 to 100);
+       signal store_filename: string(1 to 100);
+
+begin
+
+       p_test: process
+       begin
+               load_filename <= str_pad("test_in.wav", 100);
+               wait for 10 ms;
+               store_filename <= str_pad("test_out2.wav", 100);
+               wait;
+       end process;
+
+
+       --e_src: entity work.proto_i2s_tx
+       --      port map (
+       --              sck => sck,
+       --              ws  => ws,
+       --              sd  => sd
+       --      );
+
+
+       e_uut_0: entity work.proto_i2s_from_wav
+               port map (
+                       sck      => sck,
+                       ws       => ws,
+                       sd       => sd,
+
+                       filename => load_filename,
+                       is_empty => is_empty
+               );
+
+
+       e_uut_1: entity work.proto_i2s_to_wav
+               port map (
+                       sck      => sck,
+                       ws       => ws,
+                       sd       => sd,
+
+                       filename => store_filename
+               );
+
+
+       e_ctrl: entity work.proto_i2s_ctrl
+               port map (
+                       mclk => mclk,
+                       sck  => sck,
+                       ws   => ws
+               );
+
+end;
index 59ec4309da6505820adfd47a4eeac061171c990a..6745d9a62cdcfabaf6b130824bdc1ead6be10d7a 100644 (file)
@@ -15,6 +15,7 @@ begin
        p_test: process
                variable v: integer := -1;
        begin
+               report integer'image(integer'high);
                -- Create queue with page size of 5, should be empty
                -- Page size is small for testing purposes, realistic size is in the thousands
                report "A";