Playing Audio CD: The Hardcore Way

How hard it would be?

  • Easy

  • Doable but will take some time

  • I want to see you fail on Reed-Solomon decoder implementation

  • Impossible


Results are only viewable after voting.
After watching Technology Connections video about audio CD:


I thought that it is actually not that hard to decode raw bitstream from an audio CD to get audio samples.
How hard could it be anyways, it is technology from more than 40 years ago, right?

So I started looking into it and first obvious things to do were:
1. Get an old philips CD player so I can get access to raw signal from the laser head
2. Read the redbook standard
3. Implement the decoder

Looks quite straitforward.

The first item was easy - I happened to be in the Netherlands so I got old Philips CD-160 at nearest kringloop for 15 Euros.
Second one is available online:
https://archive.org/details/RedBook...stemIEC60908SecondEdition199902ISBN2831846382

The video actually does a good job of explaining the high-level picture. Some diagrams do look horrifying at first:
decoder.png


But actually it is just a bit of data manipulation and two Reed-Solomon decoders.
decoder_simplify.png


And also I'm wondering, how many community members are interested in such experiment? :)
 
  • Like
Reactions: 2 users
You need a motor speed control, that is some kind of PLL, also a memory for intermediate storage of data. All these and the error correction, subcode processing, etc. are implemented in a single chip. What extra do you intend to add? It could be interesting for education purposes.
 
  • Like
Reactions: 1 user
You need a motor speed control, that is some kind of PLL, also a memory for intermediate storage of data.
Let's go step by step.

First, the initial task with the HF signal (the term referring to the signal from the photodiodes in the standard) is to recover the clock so that the data can be correctly sampled. It's important to note that at this stage, the signal is analog.

This process is known as Clock and Data Recovery (CDR). There are various techniques for achieving this, with one of the simplest methods (commonly used in older CD players) involving a PLL (Phase-Locked Loop) that synchronizes with the incoming data stream. However, for the PLL to establish a reliable lock, the data stream must meet specific requirements, notably having a minimum number of transitions within a given unit of time. Failure to meet this requirement can result in the PLL losing lock, leading to data recovery failure.

Additionally, there needs to be a secondary loop to regulate the speed of the disk rotation. This is typically implemented at a higher level using a servo loop. After the serial stream is converted into parallel data packets (frames), they are stored in a FIFO (First-In-First-Out) buffer to ensure that subsequent stages of the decoder can access them as needed. This essentially creates two clock domains: the data is written in the HF clock domain and read in the data decoder clock domain. If the spindle speed is too low, the FIFO may become empty, while if it spins too fast, it may overflow. Therefore, the task of the servo loop is to maintain the FIFO fill level at the desired value, for instance - 50%.

servo.png


All these and the error correction, subcode processing, etc. are implemented in a single chip. What extra do you intend to add? It could be interesting for education purposes.

All that is part of the decoder, yes. Currently I limit scope to the data decoder only, including subcode. The extra things that I want to see is how often the C1 and C2 frames errors really happen in real life and how effective the RS codes are.
 
Last edited:
This project amounts to designing the read logic in a disk controller. The PLL is an analog circuit and the rest is straightforward digital logic. 74LS TTL is more than fast enough.

BTW, if you just want to get the data from an audio CD, use a computer to extract it to a WAV file. WAV files are easy to read, consisting of a header followed by 16-bit signed audio data.
Ed
 
Last edited:
and the rest is straightforward digital logic. 74LS TTL is more than fast enough.
What, those are decades obsolete! 74HC or 74LVC perhaps, this isn't the 1900's! 3.3V logic is probably the best approach now, and much could be done using microcontroller + CPLD/FPGA if required. I suspect the PLL can be done digitally even, there's a lot of inertia in a spinning disc so update rate need not be super high, besides the data has to be queued in a FIFO somewhere to sort out the wow/flutter.

For a more constrained challenge try to do as much as possible on an RP2040, I suspect its powerful enough and being dual-core that's got to help with tight timing issues.
 
However, for the PLL to establish a reliable lock, the data stream must meet specific requirements, notably having a minimum number of transitions within a given unit of time.

Isn't that guaranteed by the eight-to-forteen modulation? For clock and data recovery, there are various kinds of variants of PLLs, for example a PLL with a Hogge detector as phase detector.
 
  • Like
Reactions: 1 user
Isn't that guaranteed by the eight-to-forteen modulation? For clock and data recovery, there are various kinds of variants of PLLs, for example a PLL with a Hogge detector as phase detector.

Yes but there are couple more things.
One frame on CD is 588 channel bits long and contains 33 bytes of data. 1 byte is for the subcodes, 24 bytes of audio data and 8 bytes of parity for error correction. After Eight-To-Fourteen (EFM) modulation each 8-bit byte converts into 14 bit word, so the data takes 33*14 = 462 channel bits.
The header patter takes 24 bits. That sums up to 486 bits. And the rest is filled with merging bits:
frame.png


(Interesting note: there is a mistake in this drawing in the Red Book standard)

The merging bits are inserted after the header and each byte, and their purpose to guarantee that criteria for minimum and maximum distance between transitions in the HF signal are satisfied. The minimum distance is 3 channel bits (shortest possible pit/land), the maximum - 11 (used in the header).

Another thing is that 0s and 1s are not coded by pits and lands but in transitions between them.

a PLL with a Hogge detector as phase detector.
Yes, that will be the easiest choice to implement, I guess
 
Alright, since my focus is on bitstream decoding, I hooked a cable to my CD160 main board to the inputs of SAA7210 decoder IC:
photo1710240671 (2).jpeg
photo1710240671 (1).jpeg
I need two signals: HF bitstream and recovered clock.

cd160.png



To acquire the data I'm going to use this FPGA board:

photo1710240671.jpeg


It has got 64mbit SRAM, 20 MSample/s ADC and a FTDI chip that would allow up to 3 MBytes/sec transfer to PC.
 
No updates for a long time since describing what and how it was done is not my strongest skill.

So, the first thing I did is captured HF data + HF clock streams at 100MSample/sec and transferred this data to PC thru FTDI chip.
This is done to enable running decoder RTL in the testbench with real bitstream from the CD:

initial begin
f_clk = $fopen("sdram_dump_clk","r");
f_data = $fopen("sdram_dump_data","r");

#10000

while (!$feof(f_clk) | !$feof(f_data)) begin
$fscanf(f_clk,"%d\n", v_clk);
$fscanf(f_data,"%d\n", v_data);
@(negedge r_clk_100M) begin
r_hf_clk <= v_clk ? 1'b1 : 1'b0;
r_hf_data <= v_data ? 1'b1 : 1'b0;
end
end
end

Once it was acquired, I could proceed with deconding, the first thing in real RTL is to sync input streams to 100MHz FPGA clock:

// sample hf clk and data on 100MHz clock posedge
always@(posedge i_clk_100MHz)
begin
r_hf_clk_sync <= {r_hf_clk_sync[6:0], i_hf_clk};
r_hf_data_sync <= {r_hf_data_sync[6:0], i_hf_data};
end

then sample the bitstream on posedges of HF clock ( I play a bit with exact moment where I sample the data)

wire w_hf_clk_posedge = ~r_hf_clk_sync[4] & r_hf_clk_sync[3];
wire w_hf_data_to_sample = r_hf_data_sync[5];

And of course we can do manchester decoding right away - it's just a simple XOR

wire w_hf_data_bitstream = r_hf_data[0]^r_hf_data[1];

And the actual code to detect the frame sync pattern and capture EFM symbols:
// sample hf data when hc clk posedge is detected
// decode manchester code
always@(posedge i_clk_100MHz)
begin
if(w_hf_clk_posedge)
begin
r_hf_data <= {r_hf_data[0], w_hf_data_to_sample};

r_bitstream <= {r_bitstream[25:0], w_hf_data_bitstream };

// look for the sync pattern
// after manchester decoding it should look like 0000_0000_00_1_0000_0000_00_1
// i.e. we are waiting for 10 consecutive 0s followed by 1, x2
if(~|r_bitstream[25:16] & r_bitstream[15] & ~|r_bitstream[14:5] & r_bitstream[4])
begin
r_framesync <= 1'b1;
r_bitcntr <= 4'h0;
r_bytecntr <= 4'h0;
end else begin
r_framesync <= 1'b0;
r_bitcntr <= r_bitcntr == 5'd16 ? 0 : r_bitcntr + 5'h1;
r_bytecntr <= r_bitcntr == 5'd16 && ~w_frame_end ? r_bytecntr + 6'h1 : r_bytecntr;
end

if(r_bitcntr == 5'd16) begin
r_efm_data <= ~w_frame_end ? r_bitstream[16:3] : r_efm_data;
r_efm_data_rdy <= ~w_frame_end;
end
else
begin
r_efm_data_rdy <= 1'b0;
end
end

end

So basically what is happening it this code:
1. Sync external signals to internal clock domain
2. Detect HF clock edges and capture Manchester-decoded bitstream
3. Look for sync pattern, if it is detected - reset byte counter and start receiving 14 bit EFM symbols (+ignore 3 merging bits)
4. Once 33 bytes received - assert frame end flag and proceed with the next frame.

The EFM decoder look-up table module is quite boring: It has 256 possible input combinations for data + 2 additional for S1 and S2 sector sync patterns
module opencdr_efm_lut
(
input [13:0] i_efm_symb,
output [7:0] o_data,

output o_s0_sync,
output o_s1_sync
);

reg [7:0] r_data;
assign o_data = r_data;
assign o_s0_sync = i_efm_symb == 14'b00100000000001;
assign o_s1_sync = i_efm_symb == 14'b00000000010010;

always@(*) begin
case(i_efm_symb)
14'b01001000100000: r_data <= 8'd0 ;
14'b10000100000000: r_data <= 8'd1 ;
14'b10010000100000: r_data <= 8'd2 ;


So this is how raw frames from the picture above are generated. The next step is de-interleaving.
I'll post entire project and raw data on github later. It is a bit messy now :)