SP/DIF transmitter project
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 | |
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.
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.
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: