interfaces component

I2S Audio Transmitter

Open in playground

A stereo I2S transmitter that streams two 16-bit samples per frame over the classic three-wire audio interface: bit clock sck, word select ws, and serial data sd. This is the format audio DACs and codecs expect on their digital input.

The bit clock runs at a quarter of clk, from a two-bit divider. A five-bit slot counter walks the 32 bit slots of a frame. Its top bit is ws directly: low while the left word streams, high for the right. Data leaves a 16-bit shift register MSB first, and every output edge lands on a falling edge of sck, so a receiver samples safely on the rising edge. The standard I2S one-bit delay falls out of the load timing. Each sample loads one slot after ws flips, so a word's last bit crosses into the next channel's first slot.

In the waveform: watch the first rise of ws while sd still carries the left word. sample_l is 0xA5C3, so its final 1 lands one slot after the flip. That one-slot overlap is the I2S signature.

In the netlist: the whole machine is one 16-bit shift chain, a two-bit divider, and a five-bit slot counter. Nothing more is needed to speak I2S.

The course builds serial interfaces from the wire up, from UART framing to bus arbitration, in Communication Interfaces.

-- I2S stereo transmitter: sck = clk/4, 32 bit slots per frame, data MSB
-- first on sck falling edges with the standard one-bit delay after ws
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity i2s_transmitter is
  port (
    clk      : in  std_logic;
    rst      : in  std_logic;
    sample_l : in  std_logic_vector(15 downto 0);
    sample_r : in  std_logic_vector(15 downto 0);
    sck      : out std_logic;
    ws       : out std_logic;
    sd       : out std_logic
  );
end entity i2s_transmitter;

architecture rtl of i2s_transmitter is
  signal div_r    : unsigned(1 downto 0);
  signal bit_r    : unsigned(4 downto 0);
  signal bit_next : unsigned(4 downto 0);
  signal shreg_r  : std_logic_vector(15 downto 0);
begin

  bit_next <= bit_r + 1;

  -- Everything advances on the clk edge that ends div_r = 3: that is the
  -- falling edge of sck, so sd and ws only ever move there.
  seq_p : process(clk)
  begin
    if rising_edge(clk) then
      if rst = '1' then
        div_r   <= (others => '0');
        bit_r   <= (others => '0');
        shreg_r <= (others => '0');
      else
        div_r <= div_r + 1;
        if div_r = 3 then
          bit_r <= bit_next;
          if bit_next = 1 then
            shreg_r <= sample_l;                 -- one slot after ws falls
          elsif bit_next = 17 then
            shreg_r <= sample_r;                 -- one slot after ws rises
          else
            shreg_r <= shreg_r(14 downto 0) & '0';
          end if;
        end if;
      end if;
    end if;
  end process seq_p;

  sck <= div_r(1);
  ws  <= bit_r(4);
  sd  <= shreg_r(15);

end architecture rtl;
Waveform loading...