From f159e03ce71e441094acb15ac8e98923dd250871 Mon Sep 17 00:00:00 2001 From: rs <> Date: Wed, 14 Jan 2026 18:00:46 -0600 Subject: [PATCH] Update I2S-WAV entities and related packages --- libraries/simulated/dev_pmod_i2s.vhd | 292 +++------------ libraries/simulated/pkg_binfile.vhd | 343 ++++++++++++++++++ libraries/simulated/pkg_intqueue.vhd | 5 - libraries/simulated/proto_i2s_ctrl.vhd | 52 +++ libraries/simulated/proto_i2s_from_wav.vhd | 102 ++++++ libraries/simulated/proto_i2s_to_wav.vhd | 101 ++++++ .../simulated/tests/test_dev_pmod_i2s.vhd | 6 +- libraries/simulated/tests/test_i2s_to_wav.vhd | 70 ++++ libraries/simulated/tests/test_intqueue.vhd | 1 + 9 files changed, 722 insertions(+), 250 deletions(-) create mode 100644 libraries/simulated/pkg_binfile.vhd create mode 100644 libraries/simulated/proto_i2s_ctrl.vhd create mode 100644 libraries/simulated/proto_i2s_from_wav.vhd create mode 100644 libraries/simulated/proto_i2s_to_wav.vhd create mode 100644 libraries/simulated/tests/test_i2s_to_wav.vhd diff --git a/libraries/simulated/dev_pmod_i2s.vhd b/libraries/simulated/dev_pmod_i2s.vhd index 9c47fa3..24c6357 100644 --- a/libraries/simulated/dev_pmod_i2s.vhd +++ b/libraries/simulated/dev_pmod_i2s.vhd @@ -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 index 0000000..b24c5f3 --- /dev/null +++ b/libraries/simulated/pkg_binfile.vhd @@ -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; diff --git a/libraries/simulated/pkg_intqueue.vhd b/libraries/simulated/pkg_intqueue.vhd index 83cbc82..4aed238 100644 --- a/libraries/simulated/pkg_intqueue.vhd +++ b/libraries/simulated/pkg_intqueue.vhd @@ -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 index 0000000..2dea600 --- /dev/null +++ b/libraries/simulated/proto_i2s_ctrl.vhd @@ -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 index 0000000..67e31ef --- /dev/null +++ b/libraries/simulated/proto_i2s_from_wav.vhd @@ -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 index 0000000..c310723 --- /dev/null +++ b/libraries/simulated/proto_i2s_to_wav.vhd @@ -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; diff --git a/libraries/simulated/tests/test_dev_pmod_i2s.vhd b/libraries/simulated/tests/test_dev_pmod_i2s.vhd index 29039da..ea82c26 100644 --- a/libraries/simulated/tests/test_dev_pmod_i2s.vhd +++ b/libraries/simulated/tests/test_dev_pmod_i2s.vhd @@ -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 index 0000000..5174ad8 --- /dev/null +++ b/libraries/simulated/tests/test_i2s_to_wav.vhd @@ -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; diff --git a/libraries/simulated/tests/test_intqueue.vhd b/libraries/simulated/tests/test_intqueue.vhd index 59ec430..6745d9a 100644 --- a/libraries/simulated/tests/test_intqueue.vhd +++ b/libraries/simulated/tests/test_intqueue.vhd @@ -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"; -- 2.43.0