SP/DIF transmitter project

From Hackerspace ACKspace
Revision as of 20:34, 29 July 2012 by Danny Witberg (talk | contribs) (VHDL design)
Jump to: navigation, search
Project: SP/DIF transmitter project
Featured:
State Completed
Members Danny Witberg
GitHub No GitHub project defined. Add your project here.
Description This project describes a lightweight SP/DIF transmitter in VHDL
Picture
No project picture! Fill in form Picture or Upload a jpeg here


Introduction

SP/DIF is a protocol for sending and receiving digital audio over an optical or electrical transmission line. It is capable of sending two channels of up to 24 bits audio at a sample rate of up to 384kHz. However, most of the times you will encounter this protocol as 16 bit 44.1kHz, being the quality of a CD. Optical connector standard for SP/DIF is a TOSlink connector, developed by Toshiba. Electrical interface uses a cinch type connector.

SP/DIF is very similar to a professional standard, AES-EBU. In fact, only the signaling bit and their meaning is diffrent from SP/DIF. An SP/DIF transmitter can be connected to an AES-EBU receiver. However, from AES-EBU to SP/DIF can give unpredictable results.

Both AES/EBU and SP/DIF originate from the AES3 standard, developed by the Audio Engineering Society. It is a biphase mark encoded bitstream to ensure that signal integrity is maintained across the whole of the transmission line. Databits are sended as 10 or 01 sequence for a '1' bit, and 00 or 11 for a '0' bit.

Biphase.gif

A frame consisting of two subframes are sent every sample. Every subframe contains, apart from the audio data from a channel, a preamble, validity, user, channel status and parity bit. The preamble breaks the rules of the biphase mark encoding for synchronisation purposes.

Subframe.gif

Biphase mark encoding through NRZI

As said, a bit can be encoded in two ways, and it depends on the preceeding signal level. The first part has to be diffrent from the last part of the preceeding level. so if the last bit ended with a high signal level, a following '1' bit is encoded like "01". If the preceeding signal level was low, it is encoded "10".

All of this is very corresponding with the NRZI standard, where a '1' is encoded as a change in signal level, and a '0' is encoded with no change. Using this NRZI scheme, you can create a biphase marked '1' by sending "11" (two consecutive changes) and a '0' as "10" (a change, then no change). In short, sending a 1, then the actual bit through a NRZI encoder, results in a biphase marked signal.

Parity generation

As SP/DIF uses even parity in its signal, every frame has an even number of ones and zeros. This means that every frame is sent using the same "signal polarity". The standard however states that both polarities have to decoded successfully because this can change by mixing up a balanced transmission line.

VHDL design

A VHDL design has been developed, as lightweight as possible, resulting in only 81 logic elements (LE's) in an Altera FPGA.

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;

entity spdif_transmitter is
 port(
  bit_clock : in std_logic; -- 128x Fsample (6.144MHz for 48K samplerate)
  data_in : in std_logic_vector(23 downto 0);
  address_out : out std_logic := '0'; -- 1 address bit means stereo only
  spdif_out : out std_logic
 );
end entity spdif_transmitter;

architecture behavioral of spdif_transmitter is

 signal data_in_buffer : std_logic_vector(23 downto 0);
 signal bit_counter : std_logic_vector(5 downto 0) := (others => '0');
 signal frame_counter : std_logic_vector(8 downto 0) := (others => '0');
 signal data_biphase : std_logic := '0';
 signal data_out_buffer : std_logic_vector(7 downto 0);
 signal parity : std_logic;
 signal channel_status_shift : std_logic_vector(23 downto 0);
 signal channel_status : std_logic_vector(23 downto 0) := "001000000000000001000000";
 
begin
 
 bit_clock_counter : process (bit_clock)
 begin
  if bit_clock'event and bit_clock = '1' then
   bit_counter <= bit_counter + 1;
  end if;
 end process bit_clock_counter;

 data_latch : process (bit_clock)
 begin
  if bit_clock'event and bit_clock = '1' then
   parity <= data_in_buffer(23) xor data_in_buffer(22) xor data_in_buffer(21) xor data_in_buffer(20) xor data_in_buffer(19) xor data_in_buffer(18) xor data_in_buffer(17)  xor data_in_buffer(16) xor data_in_buffer(15) xor data_in_buffer(14) xor data_in_buffer(13) xor data_in_buffer(12) xor data_in_buffer(11) xor data_in_buffer(10) xor data_in_buffer(9) xor data_in_buffer(8) xor data_in_buffer(7) xor data_in_buffer(6) xor data_in_buffer(5) xor data_in_buffer(4) xor data_in_buffer(3) xor data_in_buffer(2) xor data_in_buffer(1) xor data_in_buffer(0) xor channel_status_shift(23);
   if bit_counter = "000011" then
    data_in_buffer <= data_in;
   end if;
   if bit_counter = "111111" then
    if frame_counter = "101111111" then
     frame_counter <= (others => '0');
    else
     frame_counter <= frame_counter + 1;
    end if;
   end if;
  end if;
 end process data_latch;

 data_output : process (bit_clock)
 begin
  if bit_clock'event and bit_clock = '1' then
   if bit_counter = "111111" then
    if frame_counter = "101111111" then -- next frame is 0, load preamble Z
     address_out <= '0';
     channel_status_shift <= channel_status;
     data_out_buffer <= "10011100";
    else
     if frame_counter(0) = '1' then -- next frame is even, load preamble X
      channel_status_shift <= channel_status_shift(22 downto 0) & '0';
      data_out_buffer <= "10010011";
      address_out <= '0';
     else -- next frame is odd, load preable Y
      data_out_buffer <= "10010110";
      address_out <= '1';
     end if;
    end if;
   else
    if bit_counter(2 downto 0) = "111" then -- load new part of data into buffer
     case bit_counter(5 downto 3) is
      when "000" =>
       data_out_buffer <= '1' & data_in_buffer(0) & '1' & data_in_buffer(1) & '1' & data_in_buffer(2) & '1' & data_in_buffer(3);
      when "001" =>
       data_out_buffer <= '1' & data_in_buffer(4) & '1' & data_in_buffer(5) & '1' & data_in_buffer(6) & '1' & data_in_buffer(7);
      when "010" =>
       data_out_buffer <= '1' & data_in_buffer(8) & '1' & data_in_buffer(9) & '1' & data_in_buffer(10) & '1' & data_in_buffer(11);
      when "011" =>
       data_out_buffer <= '1' & data_in_buffer(12) & '1' & data_in_buffer(13) & '1' & data_in_buffer(14) & '1' & data_in_buffer(15);
      when "100" =>
       data_out_buffer <= '1' & data_in_buffer(16) & '1' & data_in_buffer(17) & '1' & data_in_buffer(18) & '1' & data_in_buffer(19);
      when "101" =>
       data_out_buffer <= '1' & data_in_buffer(20) & '1' & data_in_buffer(21) & '1' & data_in_buffer(22) & '1' & data_in_buffer(23);
      when "110" =>
       data_out_buffer <= "10101" & channel_status_shift(23) & "1" & parity;
      when others =>
     end case;
    else
     data_out_buffer <= data_out_buffer(6 downto 0) & '0';
    end if;
   end if;
  end if;
 end process data_output;
 
 biphaser : process (bit_clock)
 begin
  if bit_clock'event and bit_clock = '1' then
   if data_out_buffer(data_out_buffer'left) = '1' then
    data_biphase <= not data_biphase;
   end if;
  end if;
 end process biphaser;
 spdif_out <= data_biphase;
 
end behavioral;

To see the transmitter core in full action, here is a picture with some real life signals:

Spdif.gif