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