From Hamsterworks Wiki!
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.
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.
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.
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:
Implemented on a breadboard it looks like:
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 (email@example.com) -- -- 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
|0-3||Preamble/Sync||Special format that breaks the normal bit flipping rules|
|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.
|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:
|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.
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.
|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
- File:Spdif out.zip - Zip of the project.
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.