SPDIF Thru

From Hamsterworks Wiki!

Jump to: navigation, search

By combining the SPDIF_Input and SPDIF out this FPGA project will allow me to play with Audio DSP algorithms. On the surface it sounds simple, but as the source is in a different clock domain it is not that simple - the CD player could be running at 44,101Hz and the FPGA could be running at 44,099Hz.

To get around this I regenerate something close to the clock of the arriving subframes and use that clock the sending subframes. By monitoring how full the a FIFO allows the output rate to be tuned every 1/10th of a second.

At the moment it only works for 44,100Hz streams, but by adjusting one constant could be adapted to work at 48,000Hz too.

Examples that could be built using this as a base platform

  • Spectrum analyzer
  • UV meter
  • Digital volume control - see SPDIF_Volume for an example
  • Digital effects (e.g. reverb)
  • Automatic volume control
  • Any sort of DSP solution
  • Data capture to a PC.
  • Digital mixer (e.g. for including pre-recorded voice-overs in a S/PDIF stream)

Contents

Source files

spdif_thru.vhd Top level module
spdif_input.vhd Samples the input signal into frames
spdif_output.vhd Sends frames out the external interface
recover_clock.vhd Recovers the clock for sampling the input frame
gen_bit_clock.vhd Generates the clock for sending the output frame
rate_matcher.vhd Updates the gen_bit_clock period every 1/10th of a second.
spdif_thru.ucf User constraints file.

Not included above is the sample_store fifo - it's just a 28 bit wide x 1024 deep FIFO IP, with a data_count output, and a DCM that doubles the clock from 50Mhz to 100MHz.

A complete build of all files is in File:SPDIF thru.zip

Block Diagram

Spdif thru.png

Interface electronics

Over coax, the signal is sent as a 0.5v peak-to-peak signal that needs conversion to LVTTL before it can be processed by an FPGA, and then back again after going thorough the FPGA. I found these schematics at http://sound.westhost.com/project85.htm:

Converting S/PDIF from coax to LVTTL

Converting S/PDIF from coax to LVTTL

A good thing is that by connecting the SPDIF_out to SPDIF_in you can test the interface without programming the FPGA.

Breadboard build

Spdif thru bb.png

Items I required:

  • 2 x RCA plugs, mounted on PCB headers
  • 1 x 0.01uf Cap
  • 1 x 0.10uf Cap (but I used a second 0.01uf one by accident and it worked...)
  • 2 x 100 ohm resisters
  • 1 x 91 ohm resister
  • 1 x 8,200 ohm resister
  • 1 x 360 ohm resister
  • 1 x 74HC04 hex inverter

I didn't need to use the exact values (eg. used 100 ohm for 91 ohm, and 380 ohm for 360 ohm) but it most probably does matter if you were designing for production or if you need to drive long cables.

Project Status

All finished.

Resource usage as reported from Xilinx WebPack ISE:

Maximum frequency: 139MHz
Number of Slice Flip Flops: 305
Number of 4 input LUTs: 345
Number of occupied Slices: 280
Block RAM: 1

2011-09-13 End-to-end testing completed

Well, it finally works! The last bug was due to me increasing the precision in the Data Rate Matcher from 8 bits to 9 bits and forgot to update a constant or two. Listened to a whole CD without any audible artifacts.

The data rate matching is working well. It takes 15 seconds to settle down, but then end up able to keep the FIFO within 1 sample per second (so it wanders about +/- 1 sample per second around the input sample. Excellent.

2011-09-11 End-to-end testing started

After an hour of testing, first audio output has been generated, but only for a second!

  • Interface electronics loopback tests OK
  • Able to get "lock" on the S/PDIF input signal
  • Fixed issue in SPDIF_out which simulated fine but didn't actually work!
  • Output signal now looks OK in Logic Analyser

I encountered some strange behaviour - the system doesn't work (no audio), but when you hit reset or download the project again you get a few seconds of Audio. So it looks like the samples are getting into the FIFO, and that the SPDIF_out is correctly able to send samples in the FIFO.

Something must be wrong with the data rate matching function, allowing the FIFO to overflow and smaples to be dropped. If samples are dropped then the S/PDIF would have an invalid number of subframes per frame, causing my receiver to mute.

Need to do some more debugging of the Data Rate Matcher but on the whole things are looking promising.

2011-09-10 Breadboard build started

Not too hard - six resisters, two caps, two sockets and one IC.

2011-09-08 Simulation testing of individual parts completed

Everything checks out, all build warnings have been investigated and resolved.

2011-09-03 Timing requirements achieved

The initial implementation only just meet 100MHz. As latency isn't a big deal for this design a few extra flip-flops were added at critical places. Timing now up to 140+ MHz.

2011-09-02 Insight into design

The resource usage is quite high, due to the 128 bit shift registers need to capture frames for both channels at once. Re-engineered to use a narrower (30 bit wide) FIFO, and now only 64 bits are required on the input and output shift registers. Design is now much smaller, and as the FIFO is filled/emptied twice as fast the data rate matching is twice as precise.

Also re-engineered the Data Rate Matcher component to be more like a 1-bit DAC, using the carry output as the bit clock for the SPDIF_out module. Works a treat!

Data rate matching

Input and output signal rates have to be matched, so samples don't get dropped or empty gaps need to be filled.

As luck would have it, the fractional clock generator / DDA is perfect for this. By adjusting the fractional value you can 'trim' the clock to nearly match the clock rate.

With a little bit of buffering this should produce an acceptable solution with only a little bit of latency due to the samples being held in the FIFO.

A software model of this data rate matching

This is the little C program I used to check my rate matching algorithm:

// spdif_sim.cpp
//
// Author: Mike Field hamster@snap.net.nz
//
// A small test program to test the ability for the SPDIF_THRU project to sync 
// a spdif input at a different rate than the expected one.
//
// TPS        = times per second to adjust the send clock
// clock    = S/PDIF generator clock
// whole,fraction,base = Values used in the frational clock generator
//
//

#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

#define TPS 10

int main(int argc, char *argv[])
{
  int whole     = 17;
  int fraction  = 631;
  int base      = 882;
  int clock     = 100000000;

  int inRate    = 44100; 
  int buffer    = 0;
  int count        = 0;
  int last        = 0;

  // Check and process the command parameters
  if(argc > 2)
  {
    fprintf(stderr,"usage: %s [in rate]\r\n");
    return 0;
  }
  
  if(argc == 2)    inRate  = atoi(argv[1]);

  if(inRate <= 0)
  {
    fprintf(stderr,"usage: %s [rate1]\r\n");
    return 0;
  }

  printf("   n: in_buffer fraction\r\n");
  last = 0;
  while(true)
  {
    int trim = 0;
    double sps = clock / 128 / (whole+(double)fraction/base);

    // Update what has happened over the sample period
    last = buffer;
    buffer += inRate/TPS;
    buffer -= (int)sps/TPS;
    if(buffer < 0) buffer = 0;
    if(buffer > 255) buffer = 255;

    // Work out how much trim is needed
    if(buffer <= last && buffer < 32)        trim = 8;
    else if(buffer < last && buffer < 120 )  trim = 1;

    if(buffer >= last && buffer > 223)       trim = -8;
    else if(buffer > last && buffer > 131)   trim = -1;
    
    // Adjust the frational clock by the trim value
    fraction += trim;

    printf("%4i: %i %i\n",count, buffer, fraction);
    Sleep(100);
    count++;
  }
  return 0;
}

It's very rough with quite a few errors, but at least it proved my design was workable much quicker than developing the VHDL.

Here's the output of it locking to a 43400Hz rate. Based on a trim signals every 1/10 of a second it takes about 3 seconds to stop "underruns" on the fifo, and 9 seconds to latch onto a input that is 2.5% out off spec. A little slow but workable

c:\> spdif_sim.exe 43400
   n: in_buffer 
   0: 0 639   << Buffer always under-runs
   1: 0 647
   2: 0 655
   3: 0 663
   4: 0 671
   5: 0 679
   6: 0 687
   7: 0 695
   8: 0 703
   9: 0 711
  10: 0 719
  11: 0 727
  12: 0 735
  13: 0 743
  14: 0 751
  15: 0 759
  16: 0 767
  17: 0 775
  18: 0 783
  19: 0 791
  20: 0 799
  21: 0 807
  22: 0 815
  23: 0 823
  24: 0 831
  25: 0 839
  26: 0 847
  27: 0 855
  28: 0 863
  29: 0 871     << No more under-runs
  30: 2 871
  31: 4 871
  32: 6 871
  33: 8 871
  34: 10 871
  35: 12 871
  36: 14 871
  37: 16 871
  38: 18 871
  39: 20 871
  40: 22 871
  41: 24 871
  42: 26 871
  43: 28 871
  44: 30 871
  45: 32 871
  46: 34 871
  47: 36 871
  48: 38 871
  49: 40 871
  50: 42 871
  51: 44 871
  52: 46 871
  53: 48 871
  54: 50 871
  55: 52 871
  56: 54 871
  57: 56 871
  58: 58 871
  59: 60 871
  60: 62 871
  61: 64 871
  62: 66 871
  63: 68 871
  64: 70 871
  65: 72 871
  66: 74 871
  67: 76 871
  68: 78 871
  69: 80 871
  70: 82 871
  71: 84 871
  72: 86 871
  73: 88 871
  74: 90 871
  75: 92 871
  76: 94 871
  77: 96 871
  78: 98 871
  79: 100 871
  80: 102 871
  81: 104 871
  82: 106 871
  83: 108 871
  84: 110 871
  85: 112 871
  86: 114 871
  87: 116 871
  88: 118 871
  89: 120 871
  90: 122 871
  91: 124 871
  92: 126 871
  93: 128 871
  94: 130 871    << Buffer half full
  95: 132 870
  96: 134 869
  97: 136 868
  98: 137 867
  99: 138 866
 100: 139 865   
 101: 140 864    << Lock achieved
 102: 140 864
 103: 140 864
 104: 140 864
 105: 140 864
 106: 140 864
 107: 140 864
 108: 140 864
 109: 140 864
 110: 140 864
 111: 140 864
 112: 140 864

Implementation

As well as the clock, input bit stream and output bit stream I'll need some status lights for this to users can see what is going on.

FPGA signals

Signal IN/OUT Description
Clock IN 50MHz system clock
SPDIF_IN IN Input from COAX to TTL S/PDIF received
SPDIF_OUT OUT Output to the TTL to COAX driver
LED(7 downto 0) OUT LEDs showing how full the FIFO is
Personal tools