wakibaki’s Huff & Puff (FLL) asynchronous reclocking SPDIF receiver

Status
Not open for further replies.
A couple of weeks ago I was having a few conversations in this thread http://www.diyaudio.com/forums/digital-source/184313-what-am-i-missing-async-reclocking.html about asynchronous reclocking.

The object of reclocking is to use a local low-jitter clock to clock data into a DAC, since there has been no end of speculation about jitter in a clock recovered from an SPDIF bitstream being audible.

Asynchronous reclocking is a bit of a problem, the existing schemes don’t really work, as far as I have been able to determine, they just substitute different kinds of jitter or other problems for the jitter.

I don’t put much credence in the idea that jitter in the recovered clock is a big problem, but there are plenty of people who think that it is. There are VCXOs available with low jitter such as the one available from Tent Labs, the problem lies in synchronizing the local clock with the incoming one without ‘infecting’ it with the jitter.

Anyway, having listened to all this moaning about jitter, and a load of argument about asynchronous USB, which is supposed not to suffer from the problem, I started to think that surely there must be a solution to the problem.

The first idea that occurred to me was to have a very large buffer, and to set the local clock a tiny fraction slow so that the buffer would just fill up continuously. This would work just fine, but there is the problem that the buffer must fill up eventually, although if it were circular this could take a very long time, but it’s not a solution that allows video synch, so it does run into problems.

The next thing that occurred to me was to reset the clock once the buffer approached full and allow it to empty. This is in fact a good solution because you can use microscopic adjustments whose effect on the pitch are below the threshold of audibility, so that although the clock runs alternately slow and fast, the alternation is at a frequency below audibility and the data-dependent jitter (or any other jitter) is reduced to the jitter inherent in the local clock, ~3ps in the case of the Tent Labs VCXO.

This reminded me of the VFOs employed in Ham radio before the appearance of swallow-counter PLL synthesizers and DDSs and which are still built today. They were called Huff & Puff VFOs and were not really the same as this design, but are similar in that they are phase insensitive and so are sometimes described as a Frequency Locked Loop.

It turns out that Dan Lavry has a similar scheme using micro's to adjust the clocks, but as far as I was concerned I invented it at the time.

Having the idea in my head, I couldn’t leave it alone at that, so although I am unlikely to build such a thing myself, I couldn’t resist drawing up a circuit and writing some VHDL for a couple of CPLDs to make it work.

This isn't a serious attempt at this design, just a demonstration of feasability.

huff&puff.jpg

If you want to see the detail, it's in the zip file.

View attachment huffpuff.zip

Circuit description and VHDL follows.

w
 
Circuit description.

The circuit as drawn consists of 2 sections which alternately access a 4-bank RAM buffer. This number of banks is not absolutely necessary, but made the design easy the first time around. It meant I could ignore read/write clashes.

A conventional SPDIF receiver provides the clock and data to a free-running serial to parallel converter implemented in a Xilinx CPLD. This fills up the banks of RAM one by one. When the last is full it starts at the beginning again. Every time the address being written hits zero, the CPLD puts out a bitclock-wide pulse.

A second CPLD runs off a 256*bitclock VCXO. It reads the zero pulse and synchronizes itself with the first CPLD. It also measures the interval between pulses and adjusts the VCXO frequency which it controls using a DAC. Reading from the first buffer only commences when the third buffer starts to be written. The second CPLD reorganises the data into a serial bitstream with bit- and L/R- clocks. The fourth buffer prevents any clash of accesses between the CPLDs, although this is a belt-and-braces measure, since the accesses are staggered and the busses tristated when not in use. A less hardware-hungry version is possible. Identical CPLDs are employed as again this cuts down on the design effort, but the receiver will fit in a smaller one. Looking at this with the hindsight of a couple of days, I can reduce the the number of RAM chips to one...

Each buffer takes just over 2 million of the high speed clocks to fill, a clock pulse therefore represents ~0.5ppm. The VCXO DAC is12-bit, the VCXO is pullable by about 200ppm, and each DAC LSB therefore represents ~0.05ppm. After an initial coarse adjustment the VCXO is adjusted one LSB every zero address, but it should settle within seconds.

The VHDL is shown here with testbenches. These can be observed in operation using the downloadable Xilinx ISE tool. A small image of the schematic is also shown, but it is too small to show detail and a zipped .pdf can be downloaded if detail is required. The implementation shows a PCM1794 DAC and a Tent Labs VCXO, but anyone contemplating building such a thing should be aware that the diagram is an unproven sketch, and further review will be required before a working model could be produced.

w
 
Some VHDL

This is the SPDIF receiver. Damn forum has messed up my formatting.


-- -------------------------------------------------------------------- --
-- -------------------------------------------------------------------- --
-- spdif_rx Xilinx CPLD --
-- -------------------------------------------------------------------- --
-- -------------------------------------------------------------------- --
-- MODULE : spdif_rx PROJECT : spdif_rx --
-- FILE : spdif_rx.vhd AUTHOR : wakibaki --
-- CREATION DATE : 23 Mar 2011 LANGUAGE : VHDL --
-- MODULE TYPE : Entity and architecture --
-- --
-- LIBRARIES : ieee.std_logic_1164 --
-- : ieee.std_logic_arith --
-- : ieee.std_logic_unsigned --
-- --
-- DESCRIPTION : This is the top level of the spdif_rx CPLD --
-- --
-- --
-- Version Initials Comment --
-- --
-- 0.1 w@w First version of code --
-- --
-- --
-- --
-- --
-- -------------------------------------------------------------------- --
-- -------------------------------------------------------------------- --
-- -------------------------------------------------------------------- --
-- -------------------------------------------------------------------- --

library IEEE;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all ;

entity SPDIF_RX is port (

NPWR_RESET : in std_logic; -- 1
GTS1 : in std_logic; -- 1
OLRCK : in std_logic; -- 1
OSCLK : in std_logic; -- 1
SDIN : in std_logic; -- 1
WE : out std_logic; -- 1
ADDR_FLAG : out std_logic; -- 1
MEM_CS : out std_logic_vector(3 downto 0); -- 3
DATA : out std_logic_vector(15 downto 0); -- 16
ADDR : out std_logic_vector(12 downto 0) -- 13

-- Total 40
);

end SPDIF_RX;

architecture SYNTH of SPDIF_RX is



signal I_ADDR : unsigned(12 downto 0);
signal I_ADDR_CTR : unsigned(3 downto 0);
signal I_OLRCK_EDGE : std_logic_vector(1 downto 0);
signal I_DATA : std_logic_vector(15 downto 0);
signal I_DATA_BYTES : std_logic_vector(15 downto 0);
signal I_MEM_CS : unsigned(3 downto 0);
signal I_Z_CTR : unsigned(3 downto 0);
signal I_ADDR_INC : std_logic;
signal I_ADDR2INC : std_logic;
signal I_ADDR_FLAG : std_logic;
signal I_WE_BUF : std_logic;
signal I_WE : std_logic;
signal I_DATA_BUF : std_logic;
signal I_FIRSTIME : std_logic;

begin

DO_IT : process(OSCLK,NPWR_RESET)

begin

if NPWR_RESET = '0' then

I_ADDR <= (others => '1');
I_ADDR_CTR <= (others => '0');
I_Z_CTR <= (others => '0');
I_DATA <= (others => '0');
I_DATA_BYTES <= (others => '0');
I_OLRCK_EDGE <= (others => '0');
I_MEM_CS <= "0111";
I_ADDR_INC <= '0';
I_ADDR2INC <= '0';
I_ADDR_FLAG <= '0';
I_WE <= '1';
I_DATA_BUF <= '0';
I_FIRSTIME <= '0';

elsif OSCLK'event and OSCLK = '1' then

I_OLRCK_EDGE(1) <= I_OLRCK_EDGE(0);
I_OLRCK_EDGE(0) <= OLRCK;
I_DATA_BUF <= SDIN;
I_DATA(14 downto 0) <= I_DATA(15 downto 1);
I_DATA(15) <= I_DATA_BUF;
I_Z_CTR <= I_Z_CTR + 1;

if I_OLRCK_EDGE = "10" or I_OLRCK_EDGE = "01" then

I_DATA_BYTES <= I_DATA;
I_ADDR_INC <= '1';

end if;

if I_ADDR_INC = '1' then

I_WE <= '0';
I_ADDR_INC <= '0';

end if;

if I_WE = '0' then

I_WE <= '1';
I_ADDR2INC <= '1';

end if;

if I_ADDR2INC = '1' then

I_ADDR <= I_ADDR + 1;
I_FIRSTIME <= '1';
I_ADDR2INC <= '0';

end if;

if I_ADDR = 0 and I_FIRSTIME = '1' then

I_ADDR_CTR <= I_ADDR_CTR + 1;

if I_ADDR_CTR = 0 then

I_ADDR_FLAG <= '1';

end if;

end if;

if I_ADDR_FLAG = '1' then

I_ADDR_FLAG <= '0';
I_MEM_CS <= I_MEM_CS rol 1;

end if;

end if;

end process;

I_WE_BUF <= I_WE when I_FIRSTIME = '1' else '1';
MEM_CS <= std_logic_vector(I_MEM_CS) when GTS1 = '1' else (others => 'Z');
DATA <= I_DATA_BYTES when (GTS1 = '1' and (I_Z_CTR = 3 or I_Z_CTR = 4 or I_Z_CTR = 5)) else (others => 'Z');
ADDR <= std_logic_vector(I_ADDR) when (GTS1 = '1' and (I_Z_CTR = 3 or I_Z_CTR = 4 or I_Z_CTR = 5)) else (others => 'Z');
WE <= I_WE_BUF when GTS1 = '1' else 'Z';
ADDR_FLAG <= I_ADDR_FLAG when GTS1 = '1' else 'Z';

end SYNTH;

w
 
And the testbench for that:- (this has an arbitrary 25ps clock)


-- ----------------------------------------------------------------------- --
-- ----------------------------------------------------------------------- --
-- spdif_rx_tb Xilinx CPLD --
-- ----------------------------------------------------------------------- --
-- ----------------------------------------------------------------------- --
-- MODULE : spdif_rx_tb PROJECT : spdif_rx_tb --
-- FILE : spdif_rx_tb.vhd AUTHOR : wakibaki --
-- CREATION DATE : 23 Mar 2011 LANGUAGE : VHDL --
-- MODULE TYPE : Entity and architecture --
-- --
-- LIBRARIES : ieee.std_logic_1164 --
-- : ieee.numeric_std_all --
-- --
-- DESCRIPTION : This is the top level of the spdif_rx CPLD --
-- --
-- --
-- Version Initials Comment --
-- --
-- 0.1 w@w First version of code --
-- --
-- --
-- --
-- --
-- ----------------------------------------------------------------------- --
-- ----------------------------------------------------------------------- --
-- ----------------------------------------------------------------------- --
-- ----------------------------------------------------------------------- --

library IEEE;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all ;

entity SPDIF_RX_TB is

end SPDIF_RX_TB;

architecture TESTBENCH of SPDIF_RX_TB is

component SPDIF_RX
port (

NPWR_RESET : in std_logic; -- 1
GTS1 : in std_logic; -- 1
OLRCK : in std_logic; -- 1
OSCLK : in std_logic; -- 1
SDIN : in std_logic; -- 1
WE : out std_logic; -- 1
ADDR_FLAG : out std_logic; -- 1
MEM_CS : out std_logic_vector(4 downto 0); -- 3
DATA : out std_logic_vector(15 downto 0); -- 16
ADDR : out std_logic_vector(12 downto 0) -- 13

-- Total 40
);

end component;

signal I_GLOBAL_RESET : std_logic; -- 1
signal I_GTS1 : std_logic; -- 1
signal I_OLRCK : std_logic; -- 1
signal I_OSCLK : std_logic; -- 1
signal I_SDIN : std_logic; -- 1
signal I_WE : std_logic; -- 1
signal I_ADDR_FLAG : std_logic; -- 1
signal I_MEM_CS : std_logic_vector(4 downto 0); -- 3
signal I_DATA : std_logic_vector(15 downto 0); -- 16
signal I_ADDR : std_logic_vector(12 downto 0); -- 13

constant CLKOSCLKPERIOD : time := 25 ps; -- 40 kHz
constant LOGIC1 : std_logic := '1';

begin

CLKGENOSCLK : process

begin

while LOGIC1 = '1' loop

I_OSCLK <= '0';

wait for CLKOSCLKPERIOD;

I_OSCLK <= '1';

wait for CLKOSCLKPERIOD;

end loop;

end process CLKGENOSCLK;

SER_DAT : process

begin

I_GLOBAL_RESET <= '0';
I_OLRCK <= '0';
I_SDIN <= '0';

wait for 10ps;

I_GLOBAL_RESET <= '1';
I_GTS1 <= '1';

wait for 40ps;

I_OLRCK <= '1';
I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_OLRCK <= '0';
I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_OLRCK <= '1';
I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_OLRCK <= '0';
I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_OLRCK <= '1';
I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_OLRCK <= '0';
I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_OLRCK <= '1';
I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_OLRCK <= '0';
I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '0';

wait for CLKOSCLKPERIOD * 2;

I_SDIN <= '1';

wait for CLKOSCLKPERIOD * 2;

I_OLRCK <= '1';

wait for 10000ps;

end process SER_DAT;

UUT : SPDIF_RX

port map
(
NPWR_RESET => I_GLOBAL_RESET,
GTS1 => I_GTS1,
OLRCK => I_OLRCK,
OSCLK => I_OSCLK,
SDIN => I_SDIN,
WE => I_WE,
ADDR_FLAG => I_ADDR_FLAG,
MEM_CS => I_MEM_CS,
DATA => I_DATA,
ADDR => I_ADDR

);

end TESTBENCH;

w

More later.
 
User constraints, simulation

#PINLOCK_BEGIN
NET "ADDR(0)" LOC = "S😛IN20";
NET "ADDR(1)" LOC = "S😛IN21";
NET "ADDR(2)" LOC = "S😛IN22";
NET "ADDR(3)" LOC = "S😛IN23";
NET "ADDR(4)" LOC = "S😛IN24";
NET "ADDR(5)" LOC = "S😛IN25";
NET "ADDR(6)" LOC = "S😛IN26";
NET "ADDR(7)" LOC = "S😛IN27";
NET "ADDR(8)" LOC = "S😛IN28";
NET "ADDR(9)" LOC = "S😛IN31";
NET "ADDR(10)" LOC = "S😛IN32";
NET "ADDR(11)" LOC = "S😛IN33";
NET "ADDR(12)" LOC = "S😛IN34";
NET "DATA(0)" LOC = "S😛IN39";
NET "DATA(1)" LOC = "S😛IN40";
NET "DATA(2)" LOC = "S😛IN41";
NET "DATA(3)" LOC = "S😛IN43";
NET "DATA(4)" LOC = "S😛IN44";
NET "DATA(5)" LOC = "S😛IN45";
NET "DATA(6)" LOC = "S😛IN46";
NET "DATA(7)" LOC = "S😛IN48";
NET "DATA(8)" LOC = "S😛IN49";
NET "DATA(9)" LOC = "S😛IN50";
NET "DATA(10)" LOC = "S😛IN51";
NET "DATA(11)" LOC = "S😛IN52";
NET "DATA(12)" LOC = "S😛IN53";
NET "DATA(13)" LOC = "S😛IN54";
NET "DATA(14)" LOC = "S😛IN56";
NET "DATA(15)" LOC = "S😛IN57";
NET "MEM_CS(0)" LOC = "S😛IN58";
NET "MEM_CS(1)" LOC = "S😛IN59";
NET "MEM_CS(2)" LOC = "S😛IN60";
NET "MEM_CS(3)" LOC = "S😛IN61";
NET "WE" LOC = "S😛IN66";
NET "OLRCK" LOC = "S😛IN110";
NET "OSClK" LOC = "S😛IN112";
NET "SDIN" LOC = "S😛IN115";
NET "ADDR_FLAG" LOC = "S😛IN117";
#PINLOCK_END

Here's the behavioural simulation, I've done the post-fit, but it takes ages to repeat. You can see the address flag just before the address goes to '0000000000000' and the serial input data '1010101010101010, '0101010101010101' and the write enable latching it into the memory. There's plenty of room for a read by the other CPLD (tristated, 'z's) before the next write. The data is written one L/R clock behind it's reception.

spdif_rx_behav.jpg

w
 
Might have been better to put a link to the code, rather than reproduce it all in the thread?

I wanted to make it accessible so you can just cut and paste it. Having looked for code examples myself I know they're thin on the ground.

Could you make the huff'n'puff time constant variable, so people can tune it to sound like wow from their favourite turntable?

Good thought. 😀 You can tune the time constant to a degree by altering the buffer size, in fact you can coordinate the step size of the feedback loop and the DAC clock, but I'm presenting this as a technology demonstrator rather than a working design, there's a lot of room for fine tuning. The huffing and puffing is ~0.05ppm in this case though, so it should be completely inaudible. It should be no more frequent than ~0.5Hz with an 8k buffer bank size.

w
 
Transmitter code

-- ----------------------------------------------------------------------- --
-- ----------------------------------------------------------------------- --
-- spdif_tx Xilinx CPLD --
-- ----------------------------------------------------------------------- --
-- ----------------------------------------------------------------------- --
-- MODULE : spdif_tx PROJECT : spdif_tx --
-- FILE : spdif_tx.vhd AUTHOR : wakibaki --
-- CREATION DATE : 23 Mar 2011 LANGUAGE : VHDL --
-- MODULE TYPE : Entity and architecture --
-- --
-- LIBRARIES : ieee.std_logic_1164 --
-- : ieee.numeric_std_all --
-- --
-- DESCRIPTION : This is the top level of the spdif_rx CPLD --
-- --
-- --
-- Version Initials Comment --
-- --
-- 0.1 w@w First version of code --
-- --
-- --
-- --
-- --
-- ----------------------------------------------------------------------- --
-- ----------------------------------------------------------------------- --
-- ----------------------------------------------------------------------- --
-- ----------------------------------------------------------------------- --

library IEEE;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all ;

entity SPDIF_TX is port (

NPWR_RESET : in std_logic; -- 1
CLK : in std_logic; -- 1
GTS1 : in std_logic; -- 1
ADDR_FLAG : in std_logic; -- 1
OLRCK : out std_logic; -- 1
OSCLK : out std_logic; -- 1
SDOUT : out std_logic; -- 1
RE : out std_logic; -- 1
MEM_CS : out std_logic_vector(3 downto 0); -- 3
DATA : in std_logic_vector(15 downto 0); -- 16
ADDR : out std_logic_vector(12 downto 0); -- 13
DAC_WE : out std_logic; -- 1
DAC : out std_logic_vector(11 downto 0) -- 13

-- Total 38
);

end SPDIF_TX;

architecture SYNTH of SPDIF_TX is

signal I_DATA : std_logic_vector(15 downto 0);
signal I2DATA : std_logic_vector(15 downto 0);
signal I_ADDR : unsigned(12 downto 0);
signal I_BIT_CLK : unsigned(7 downto 0);
signal I2BIT_CLK : unsigned(2 downto 0);
signal I_DAC_CALC : unsigned(21 downto 0);
signal I_TX_SREG : unsigned(15 downto 0);
signal I_REMAINDER : unsigned(21 downto 0);
signal I_S_REMAINDER : unsigned(11 downto 0);
signal I_T_REMAINDER : unsigned(11 downto 0);
signal I_ADDR_EDGE : std_logic_vector(1 downto 0);
signal I_MEM_CS : unsigned(3 downto 0);
signal I_DAC : unsigned(11 downto 0);
signal I_ENB : std_logic;
signal I_DAC_WE : std_logic;
signal I_FIRST_EDGE : std_logic;
signal I_SEC_EDGE : std_logic;
signal I_3RD_EDGE : std_logic;
signal I_GREATER_FLAG : std_logic;
signal I_LESS_FLAG : std_logic;
signal I2GREATER_FLAG : std_logic;
signal I2LESS_FLAG : std_logic;
signal I3GREATER_FLAG : std_logic;
signal I3LESS_FLAG : std_logic;
signal I_RE : std_logic;
signal I_FIRSTIME : std_logic;
signal I2FIRSTIME : std_logic;

begin

DO_IT : process(CLK,NPWR_RESET)

begin

if NPWR_RESET = '0' then

I_ADDR <= (others => '1');
I_ENB <= '0';
I_FIRST_EDGE <= '0';
I_SEC_EDGE <= '0';
I_3RD_EDGE <= '0';
I_TX_SREG <= (others => '0');
I_DAC_CALC <= (others => '0');
I_MEM_CS <= "1110";
I_BIT_CLK <= (others => '1');
I_DAC <= "100000000000";
I_ADDR_EDGE <= (others => '0');
I_DATA <= (others => '0');
I_REMAINDER <= (others => '0');
I_S_REMAINDER <= (others => '0');
I2DATA <= (others => '0');
I_GREATER_FLAG <= '0';
I2GREATER_FLAG <= '0';
I3GREATER_FLAG <= '0';
I_FIRSTIME <= '0';
I2FIRSTIME <= '0';
I_LESS_FLAG <= '0';
I2LESS_FLAG <= '0';
I3LESS_FLAG <= '0';
I_RE <= '1';
I_DAC_WE <= '1';

elsif CLK'event and CLK = '1' then

I_ADDR_EDGE(1) <= I_ADDR_EDGE(0);
I_ADDR_EDGE(0) <= ADDR_FLAG;
I_DAC_CALC <= I_DAC_CALC + 1;

if I_DAC_CALC= 4 then

I_DAC_WE <= '0';

else

I_DAC_WE <= '1';

end if;

if I_ENB = '1' then

if I_GREATER_FLAG = '1' then

I_S_REMAINDER(0) <= '0';
I_T_REMAINDER(2 downto 0) <= (others => '0');
I_S_REMAINDER(11 downto 1) <= I_REMAINDER(10 downto 0);
I_T_REMAINDER(11 downto 3) <= I_REMAINDER(8 downto 0);
I_GREATER_FLAG <= '0';
I2GREATER_FLAG <= '1';

end if;

if I2GREATER_FLAG = '1' then

I_DAC <= I_DAC - I_T_REMAINDER;
I2GREATER_FLAG <= '0';
I3GREATER_FLAG <= '1';

end if;

if I3GREATER_FLAG = '1' then

I_DAC <= I_DAC + I_S_REMAINDER;
I3GREATER_FLAG <= '0';

end if;

if I_LESS_FLAG = '1' then

I_S_REMAINDER(0) <= '0';
I_T_REMAINDER(2 downto 0) <= (others => '0');
I_S_REMAINDER(11 downto 1) <= I_REMAINDER(10 downto 0);
I_T_REMAINDER(11 downto 3) <= I_REMAINDER(8 downto 0);
I_LESS_FLAG <= '0';
I2LESS_FLAG <= '1';

end if;

if I2LESS_FLAG = '1' then

I_DAC <= I_DAC - I_T_REMAINDER;
I2LESS_FLAG <= '0';
I3LESS_FLAG <= '1';

end if;

if I3LESS_FLAG = '1' then

I_DAC <= I_DAC - I_S_REMAINDER;
I3LESS_FLAG <= '0';

end if;

I_BIT_CLK <= I_BIT_CLK + 1;
I2BIT_CLK <= I2BIT_CLK + 1;

if I_FIRSTIME = '0' then

I_FIRSTIME <= '1';

end if;

if I2BIT_CLK = 0 and I_FIRSTIME = '1' then

I_TX_SREG(14 downto 0) <= I_TX_SREG(15 downto 1);

end if;

end if;

if I_BIT_CLK = 0 or I_BIT_CLK = 128 then

I_ADDR <= I_ADDR + 1;

if I2FIRSTIME = '1' then

I_TX_SREG <= unsigned(I_DATA);
I_DATA <= I2DATA;

else

I2FIRSTIME <= '1';

end if;

end if;

if I_BIT_CLK = 1 or I_BIT_CLK = 2 or I_BIT_CLK = 3 or I_BIT_CLK = 129 or I_BIT_CLK = 130 or I_BIT_CLK = 131 then

I_RE <= '0';

else

I_RE <= '1';

end if;

if I_BIT_CLK = 2 or I_BIT_CLK = 130 then

I2DATA <= DATA;

end if;

if I_ADDR_EDGE = "01" then

if I_FIRST_EDGE = '0' then

I_FIRST_EDGE <= '1';

else

if I_SEC_EDGE = '0' then

I_SEC_EDGE <= '1';

else

if I_3RD_EDGE = '0' then

I_3RD_EDGE <= '1';
I_BIT_CLK <= (others => '0');
I2BIT_CLK <= (others => '0');
I_ADDR <= (others => '1');
I_ENB <= '1';

if I_DAC_CALC > 2097152 then

I_REMAINDER <= I_DAC_CALC - 2097152;
I_GREATER_FLAG <= '1';

end if;

if I_DAC_CALC < 2097152 then

I_REMAINDER <= 2097152 - I_DAC_CALC;
I_LESS_FLAG <= '1';

end if;

else

if I_DAC_CALC > 2097152 then

I_DAC <= I_DAC - 1;

end if;

if I_DAC_CALC < 2097152 then

I_DAC <= I_DAC + 1;

end if;

end if;

end if;

end if;

I_DAC_CALC <= (others => '0');

end if;

end if;


end process;

DAC <= std_logic_vector(I_DAC) when GTS1 = '1' and (I_DAC_CALC = 4 or I_DAC_CALC = 5 or I_DAC_CALC = 6) else (others => 'Z');
MEM_CS <= std_logic_vector(I_MEM_CS) when GTS1 = '1' else (others => 'Z');
ADDR <= std_logic_vector(I_ADDR) when GTS1 = '1' and I_RE = '0' else (others => 'Z');
RE <= I_RE when GTS1 = '1' else 'Z';
DAC_WE <= I_DAC_WE when GTS1 = '1' else 'Z';
OSCLK <= not I_BIT_CLK(2) when GTS1 = '1' else 'Z';
OLRCK <= not I_BIT_CLK(7) when GTS1 = '1' else 'Z';
SDOUT <= I_TX_SREG(0) when GTS1 = '1' else 'Z';

end SYNTH;

w
 
User constraints for tx

#PINLOCK_BEGIN
NET "ADDR(0)" LOC = "S😛IN20";
NET "ADDR(1)" LOC = "S😛IN21";
NET "ADDR(2)" LOC = "S😛IN22";
NET "ADDR(3)" LOC = "S😛IN23";
NET "ADDR(4)" LOC = "S😛IN24";
NET "ADDR(5)" LOC = "S😛IN25";
NET "ADDR(6)" LOC = "S😛IN26";
NET "ADDR(7)" LOC = "S😛IN27";
NET "ADDR(8)" LOC = "S😛IN28";
NET "ADDR(9)" LOC = "S😛IN31";
NET "ADDR(10)" LOC = "S😛IN32";
NET "ADDR(11)" LOC = "S😛IN33";
NET "ADDR(12)" LOC = "S😛IN34";
NET "DATA(0)" LOC = "S😛IN39";
NET "DATA(1)" LOC = "S😛IN40";
NET "DATA(2)" LOC = "S😛IN41";
NET "DATA(3)" LOC = "S😛IN43";
NET "DATA(4)" LOC = "S😛IN44";
NET "DATA(5)" LOC = "S😛IN45";
NET "DATA(6)" LOC = "S😛IN46";
NET "DATA(7)" LOC = "S😛IN48";
NET "DATA(8)" LOC = "S😛IN49";
NET "DATA(9)" LOC = "S😛IN50";
NET "DATA(10)" LOC = "S😛IN51";
NET "DATA(11)" LOC = "S😛IN52";
NET "DATA(12)" LOC = "S😛IN53";
NET "DATA(13)" LOC = "S😛IN54";
NET "DATA(14)" LOC = "S😛IN56";
NET "DATA(15)" LOC = "S😛IN57";
NET "MEM_CS(0)" LOC = "S😛IN58";
NET "MEM_CS(1)" LOC = "S😛IN59";
NET "MEM_CS(2)" LOC = "S😛IN60";
NET "MEM_CS(3)" LOC = "S😛IN61";
NET "RE" LOC = "S😛IN66";
NET "DAC_WE" LOC = "S😛IN94";
NET "DAC(11)" LOC = "S😛IN95";
NET "DAC(10)" LOC = "S😛IN96";
NET "DAC(9)" LOC = "S😛IN97";
NET "DAC(8)" LOC = "S😛IN98";
NET "DAC(7)" LOC = "S😛IN100";
NET "DAC(6)" LOC = "S😛IN101";
NET "DAC(5)" LOC = "S😛IN102";
NET "DAC(4)" LOC = "S😛IN103";
NET "DAC(3)" LOC = "S😛IN104";
NET "DAC(2)" LOC = "S😛IN105";
NET "DAC(1)" LOC = "S😛IN106";
NET "DAC(0)" LOC = "S😛IN107";
NET "OLRCK" LOC = "S😛IN110";
NET "OSClK" LOC = "S😛IN112";
NET "SDOUT" LOC = "S😛IN115";
NET "ADDR_FLAG" LOC = "S😛IN117";
#PINLOCK_END

w
 
You could use the
Code:
 tags (it's the "#" symbol in the advanced forum editor).
Something like that:

[CODE]-- ----------------------------------------------------------------------- --
-- ----------------------------------------------------------------------- --
-- spdif_tx Xilinx CPLD --
-- ----------------------------------------------------------------------- --
-- ----------------------------------------------------------------------- --
-- MODULE : spdif_tx PROJECT : spdif_tx --
-- FILE : spdif_tx.vhd AUTHOR : wakibaki --
-- CREATION DATE : 23 Mar 2011 LANGUAGE : VHDL -- 
-- MODULE TYPE : Entity and architecture --
-- --
-- LIBRARIES : ieee.std_logic_1164 --
-- : ieee.numeric_std_all --
-- --
-- DESCRIPTION : This is the top level of the spdif_rx CPLD --
-- --
-- --
-- Version Initials Comment --
-- --
-- 0.1 w@w First version of code --
-- --
-- --
-- --
-- --
-- ----------------------------------------------------------------------- --
-- ----------------------------------------------------------------------- --
-- ----------------------------------------------------------------------- --
-- ----------------------------------------------------------------------- --

library IEEE;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all ;

entity SPDIF_TX is port (

NPWR_RESET : in std_logic; -- 1
CLK : in std_logic; -- 1
GTS1 : in std_logic; -- 1
ADDR_FLAG : in std_logic; -- 1
OLRCK : out std_logic; -- 1
OSCLK : out std_logic; -- 1
SDOUT : out std_logic; -- 1
RE : out std_logic; -- 1
MEM_CS : out std_logic_vector(3 downto 0); -- 3
DATA : in std_logic_vector(15 downto 0); -- 16
ADDR : out std_logic_vector(12 downto 0); -- 13
DAC_WE : out std_logic; -- 1
DAC : out std_logic_vector(11 downto 0) -- 13

-- Total 38
);

end SPDIF_TX;

architecture SYNTH of SPDIF_TX is
 
tx testbench, behavioural sim

Code:
-- ----------------------------------------------------------------------- --
-- ----------------------------------------------------------------------- --
--                            spdif_rx Xilinx CPLD                         --
-- ----------------------------------------------------------------------- --
-- ----------------------------------------------------------------------- --
--   MODULE         : spdif_rx_tb          PROJECT    : spdif_rx_tb        --
--   FILE           : spdif_rx_tb.vhd      AUTHOR     : wakibaki           --
--   CREATION DATE  : 23 Mar 2011             LANGUAGE   : VHDL               -- 
--   MODULE TYPE    : Entity and architecture                              --
--                                                                         --
--   LIBRARIES      : ieee.std_logic_1164                                  --
--                  : ieee.numeric_std_all                                 --
--                                                                         --
--   DESCRIPTION    : This is the top level of the spdif_rx CPLD           --
--                                                                         --
--                                                                         --
--   Version  Initials  Comment                                            --
--                                                                         --
--   0.1      w@w       First version of code                              --
--                                                                         --
--                                                                         --
--                                                                         --
--                                                                         --
-- ----------------------------------------------------------------------- --
-- ----------------------------------------------------------------------- --
-- ----------------------------------------------------------------------- --
-- ----------------------------------------------------------------------- --

library IEEE;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all ;

entity SPDIF_RX_TB is

end SPDIF_RX_TB;

architecture TESTBENCH of SPDIF_RX_TB is

component SPDIF_TX
port (

  NPWR_RESET            : in std_logic;                          --  1
  CLK                   : in std_logic;                          --  1
  GTS1                  : in std_logic;                          --  1
  ADDR_FLAG             : in std_logic;                          --  1
  OLRCK                 : out std_logic;                         --  1
  OSCLK                 : out std_logic;                         --  1
  SDOUT                 : out std_logic;                         --  1
  RE                    : out std_logic;                         --  1
  DAC_WE                : out std_logic;                         --  1
  MEM_CS                : out std_logic_vector(3 downto 0);      --  3
  DATA                  : in std_logic_vector(15 downto 0);     --  16
  ADDR                  : out std_logic_vector(12 downto 0);     --  13
  DAC                   : out std_logic_vector(11 downto 0)     --  12
                                                      -- Total    52
);




end component;

  signal  I_GLOBAL_RESET            : std_logic;                         --  1
  signal  I_CLK                     : std_logic;                         --  1
  signal  I_GTS1                    : std_logic;                         --  1
  signal  I_ADDR_FLAG               : std_logic;                         --  1
  signal  I_OLRCK                   : std_logic;                         --  1
  signal  I_OSCLK                   : std_logic;                         --  1
  signal  I_SDOUT                   : std_logic;                         --  1
  signal  I_RE                      : std_logic;                         --  1
  signal  I_DAC_WE                  : std_logic;                         --  1
  signal  I_MEM_CS                  : std_logic_vector(3 downto 0);      --  3
  signal  I_DATA                    : std_logic_vector(15 downto 0);     --  16
  signal  I_ADDR                    : std_logic_vector(12 downto 0);     --  13
  signal  I_DAC                     : std_logic_vector(11 downto 0);     --  13

  constant CLKCLKPERIOD : time := 44 ns; -- 11.x MHz
  constant LOGIC1         : std_logic := '1';

begin

  CLKGENCLK : process 

    begin

    while LOGIC1 = '1' loop

      I_CLK <= '0';

      wait for CLKCLKPERIOD;

      I_CLK <= '1';

      wait for CLKCLKPERIOD;

    end loop;

  end process CLKGENCLK; 
  
  PAR_DAT : process
 
  begin
 
  I_GLOBAL_RESET <= '0';

  wait for CLKCLKPERIOD * 2;
  
  I_GLOBAL_RESET <= '1';
  I_GTS1         <= '1';
  I_ADDR_FLAG    <= '0';
  I_DATA         <= "0101010101010111";
  
  wait for CLKCLKPERIOD * 2;
  
  I_ADDR_FLAG    <= '1';

  wait for CLKCLKPERIOD * 2;
  
  I_ADDR_FLAG    <= '0';

  wait for CLKCLKPERIOD * 4194304;
  
  I_ADDR_FLAG    <= '1';

  wait for CLKCLKPERIOD * 2;
  
  I_ADDR_FLAG    <= '0';

  wait for CLKCLKPERIOD * 4194294;
  
  I_ADDR_FLAG    <= '1';

  wait for CLKCLKPERIOD * 2;
  
  I_ADDR_FLAG    <= '0';

  wait for CLKCLKPERIOD * 256;
  
  I_DATA <= "1010101010101000";

  wait for CLKCLKPERIOD * 256;
  
  I_DATA <= "1111111100000000";

  wait for CLKCLKPERIOD * 256;
  
  I_DATA <= "1111000011110000";

  wait for CLKCLKPERIOD * 4193536;
  wait for CLKCLKPERIOD * 2;
  
  I_ADDR_FLAG    <= '1';

  wait for CLKCLKPERIOD * 2;
  
  I_ADDR_FLAG    <= '0';

  wait for CLKCLKPERIOD * 4194304;
  
  end process PAR_DAT;

UUT : SPDIF_TX

port map
   (
  NPWR_RESET       =>  I_GLOBAL_RESET,
  CLK              =>  I_CLK,
  GTS1             =>  I_GTS1,
  ADDR_FLAG        =>  I_ADDR_FLAG,
  OLRCK            =>  I_OLRCK,
  OSCLK            =>  I_OSCLK,
  SDOUT            =>  I_SDOUT,
  RE               =>  I_RE,
  DAC_WE           =>  I_DAC_WE,
  MEM_CS           =>  I_MEM_CS,
  DATA             =>  I_DATA,
  ADDR             =>  I_ADDR,
  DAC              =>  I_DAC
 
);

end TESTBENCH;

Here you can see the staggered reads and the clock DAC value changing due to the interval between ADDR_FLAG pulses which I've manipulated in the testbench. Again the transmit data is delayed by one L/R clock from the read.

spdif_tx_behav.jpg

w
 
Status
Not open for further replies.