---------------------------------------------------------------------------------
--- 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;
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
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
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;
-- 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;
--- /dev/null
+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;
-library ieee;
-use ieee.std_logic_1164.all;
-use ieee.numeric_std.all;
-
-
package pkg_intqueue is
-- Internal types (do not use)
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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;
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
--- /dev/null
+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;
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";