SPDIF out

From Hamsterworks Wiki!

Jump to: navigation, search

Generating S/PDIF will be tricky - but it will then allow real time generation of high quality audio.

This FPGA Project was completed July 2011.

Contents

What is S/PDIF?

It is the digital audio output from CD's, PCs and other consumer devices.

In brief, it consists of a stream of subframes, each containing a header (equivilent in length to 4 bits), a 24 bit signed audio sample and 4 bits of subcode data.

The encoding is such that there each frame is encoded into 64 clock cycles (2 per bit). The signal always 'flips' between each data bit, and it also flips in the middle of a '1' bit. The bit stream 11001010 will get encoded as either 10-10-11-00-01-11-01-11 or 01-01-00-11-10-00-10-00. So a 44,100Hz stream will actually consist of 32 bits per subframe * 2 clocks per bit * 2 channels * 44,100 samples per second gives a S/PDIF signaling rate of 5,644,800Hz.

To provide syncronisation of subframes, three header patterns are used - 00010111, 00011011, and 00011101 (and their inversions 11101000, 11100100, 11100010). Because these patterns break the usual rules of a signal change every other cycle it can be used to syncronise to the start of a subframe. The three different headers indicate which channel the subframe sample is for, and if the subframe is the start of a frames.

A S/PDIF frame

Why would you? what is it good for?

The possibilities are endless... here are some things that it could be the basis for:

  • Digital sound file playback for audio players
  • High quality output for a FPGA based musical instrument
  • Audio sweep generator or frequency generator for testing speaker crossovers
  • Test platform for DSP effects for audio sources
  • Combine with S/PDIF capture and make an auto-mute for loud advertisements
  • Direct Digital Synthesis of waveforms
  • Generating binaural beats
  • Test source for S/PDIF
  • Adjusting contents of a S/PDIF bitstream's channel data in real time.

Electrical interface

Over coax the signal is sent as a 0.5v peak-to-peak, but I have LVTTL coming from the FPGA. I convert the signal using this schematic I found at http://sound.westhost.com/project85.htm:

Converting S/PDIF from LVTTL to coax

Implemented on a breadboard it looks like:

Converting S/PDIF from LVTTL to coax

How I generate the timebase for the SPDIF signal

My FPGA board has a 50MHz clock, and I need to output a signal at 5,644,800Hz.

According to http://www.hardwarebook.info/S/PDIF#Jitter_specifications_of_AES.2FEBU_interface the The AES/EBU standard for serial digital audio uses typically 163 ns clock rate and allows up to -20 ns of jitter in the signal. This can't be achieved with just a 50MHz clock, which can only generate a signal to within 20ns, and then needs to have its own jitter added.

But if I use a 100MHz clock I can generate it to within 10ns (+/- 5ms). I'll need to output a bit every 100,000,000/5,644,800 = 17.71542... cycles. This could be done using the digital differential analyzer (DDA) algorithm, as it is much like drawing angled lines on a bit-mapped display. See http://en.wikipedia.org/wiki/Digital_differential_analyzer_(graphics_algorithm) for more info on how a DDA works.

First step is to simplify the fraction to get the constants used in the DDA.

100,000,000/5,644,800
17 + 4,038,400/5,644,800
17 + 1,262/1,764
17 + 631/882

The generic method goes something like this:

 whole_cycles  = 17
 error_base    = 882
 error_per_bit = 631
 total_error   = 0;
 while true
   output a bit
   total_error = total_error + error_per_bit
   if total_error >= error_base then
     total_error = total_error - error_base
     wait one cycle
   end if
   wait whole_cycles cycles  
 end while

A little bit of re-arranging makes it friendlier to implementing in hardware, and then by plugging in the constants, and here it is implemented in VHDL:

----------------------------------------------------------------------------------
-- Engineer: Mike Field (hamster@snap.net.nz)
-- 
-- Module Name:    Timebase - Behavioral 
-- Description: Generates bit clock signals for a SPDIF output
--
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity Timebase is
    Port ( clk : in  STD_LOGIC;
           bitclock : out  STD_LOGIC);
end Timebase;

architecture Behavioral of Timebase is
   type reg is record
      state      : std_logic_vector(4 downto 0);
      errorTotal : std_logic_vector(9 downto 0);
      bitClock   : std_logic;
   end record;

   signal r : reg := ((others => '0'), (others => '0'), '0');
   signal n : reg;
   
   constant terminalCount : natural := 882;
   constant errorStep      : natural := 631;
begin

   bitClock <= r.bitClock;
   process(clk,r)
   begin
      n <= r;
      n.bitclock <= '0';
      n.state <= r.state+1;
      case r.state is
         when "00000" =>
            n.bitclock <= '1';
         when "10000" =>
            if r.errorTotal < terminalCount - errorStep then
               n.state <= "00000";
               n.errorTotal <= r.errorTotal + errorStep;
            else
               n.errorTotal <= r.errorTotal + errorStep - terminalCount;
            end if;
         when "10001" =>            
            n.state <= "00000";
         when others =>
            n.state <= r.state+1;
      end case;
   end process;
   
   process(clk, n)
   begin
      if clk'event and clk = '1' then
         r <= n;
      end if;
   end process;
end Behavioral;

This is changed slightly for the final project, but it was used for testing the timing. By adjusting the numerator (errorStep in the code above) you could trim the signal speed to match an incoming signal - see SPDIF for how to capture an incoming stream...

Converting the samples into the on-wire protocol

The incoming samples need to be framed up into SPDIF frames

bits Usage Implementation notes
0-3 Preamble/Sync Special format that breaks the normal bit flipping rules
4-7 Auxiliary-audio-databits Just zeros
8-27 Sample (LSB-MSB) Sample to be transmitted
28 Validity Must be 0 (0 = valid)
29 Subcode-data Zeros are all fine.
30 Channel-status-information Can be all zeros if you don't mind a copy protected stream
31 Parity (bit 0-3 are not included) Calculated on the fly

Apart from the preamble the bits are sent out as follows for each bit.

Bit Signal
0 Flip, wait 2 periods
1 flip, wait 1 period, flip, wait 1 period


To allow syncing up the preamble breaks these rules. It is sent as follows:

Preamble Signal
B (Channel A, start of frame) Flip, wait 3 periods, flip, wait 1 period, flip, wait 1 period, flip, wait 3 periods
M (Channel A, rest of frame) Flip, wait 3 periods, flip, wait 3 periods, flip, wait 1 period, flip, wait 1 period
W (Other channel) Flip, wait 3 periods, flip, wait 2 periods, flip, wait 1 period, flip, wait 2 periods

Because in my implementation the bits are ejected from the LSB (right) end of a 64 bit shift register all these patterns are reversed in my design.

Implementation

The implementation is relatively simple. It could most probably be optimised quite a bit. At the moment most of the resources is used to hold the wave table.

Below is the interface into the S/PDIF transmitter. For most applications you may want to put a FIFO in front of this.

Name signal Type Usage
clk100M in STD_LOGIC 100MHz clock
auxAudioBits in STD_LOGIC_VECTOR (3 downto 0) Aux Audio bits (bit 20-23 of 24 bit audio)
sample in STD_LOGIC_VECTOR (19 downto 0) 20 bits of audio sample
nextSample out STD_LOGIC Change the sample to the next value when this is asserted
channelA out STD_LOGIC Do I want a sample for channel A when "nextSample" is asserted
spdifOut out STD_LOGIC) The S/PDIF stream

The source files are:

  • spdif_out.vhd Top level module
  • soundSource.vdh The sine wave to play back
  • serialiser.vhd Convert the samples into on-wire bits
  • timebase.vhd Keeps track of the clock ticks and signals when to output the enxt bit from the shift register
  • IP core to convert the 50MHz clock to 100MHz for the project
  • spdif_out.ucf User constraints file - the clock and output onto IO1 on the FX connector.

And here is a zip file of the whole project

Testing

Testing on a Pioneer Receiver model VSXD710S. Worked fine. As I don't use the channel status bits as per the S/PDIF spec (they are all zeros) it might not be 100% compatible with some devices, especially those that honour the copy protection.

Personal tools