init
This commit is contained in:
8
lib_spdif/lib_spdif/.makefile
Normal file
8
lib_spdif/lib_spdif/.makefile
Normal file
@@ -0,0 +1,8 @@
|
||||
all:
|
||||
@echo "** Module only - only builds as part of application **"
|
||||
|
||||
|
||||
clean:
|
||||
@echo "** Module only - only builds as part of application **"
|
||||
|
||||
|
||||
177
lib_spdif/lib_spdif/api/spdif.h
Normal file
177
lib_spdif/lib_spdif/api/spdif.h
Normal file
@@ -0,0 +1,177 @@
|
||||
// Copyright 2014-2023 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
#ifndef _SPDIF_H_
|
||||
#define _SPDIF_H_
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <xs1.h>
|
||||
|
||||
/** This constant provides a mask for the bits that should be used when
|
||||
* inspecting the preamble of a sample
|
||||
*/
|
||||
#define SPDIF_RX_PREAMBLE_MASK (0xC)
|
||||
|
||||
/** This constant defines the four least-significant bits of the first
|
||||
* sample of a frame (typically a sample from the left channel)
|
||||
*/
|
||||
#define SPDIF_FRAME_X (0xC)
|
||||
|
||||
/** This constant defines the four least-significant bits of the second or
|
||||
* later sample of a frame (typically a sample from the right channel,
|
||||
* unless there are more than two channels)
|
||||
*/
|
||||
#define SPDIF_FRAME_Y (0x0)
|
||||
|
||||
/** This constant defines the four least-significant bits of the first
|
||||
* sample of the first frame of a block (typically a sample from the left
|
||||
* channel)
|
||||
*/
|
||||
#define SPDIF_FRAME_Z (0x8)
|
||||
|
||||
/* Helper macros for inspecting preambles */
|
||||
#define SPDIF_IS_FRAME_X(x) ((x & SPDIF_RX_PREAMBLE_MASK) == SPDIF_FRAME_X)
|
||||
#define SPDIF_IS_FRAME_Y(x) ((x & SPDIF_RX_PREAMBLE_MASK) == SPDIF_FRAME_Y)
|
||||
#define SPDIF_IS_FRAME_Z(x) ((x & SPDIF_RX_PREAMBLE_MASK) == SPDIF_FRAME_Z)
|
||||
|
||||
/* Helper macro for extracting sample bits from received S/PDIF subframe */
|
||||
#define SPDIF_RX_EXTRACT_SAMPLE(x) ((x & 0xFFFFFFF0) << 4)
|
||||
|
||||
/** S/PDIF receive function.
|
||||
*
|
||||
* This function provides an S/PDIF receiver component.
|
||||
* It is capable of receiving 44100, 48000, 88200, 96000, 176400 and 192000 Hz sample rates.
|
||||
*
|
||||
* The receiver will modifiy the divider of the clock-block to lock to the incoming sample rate.
|
||||
*
|
||||
* \param p S/PDIF input port.
|
||||
*
|
||||
* \param c Channel to connect to the application.
|
||||
*
|
||||
* \param clk A clock block used internally to clock data.
|
||||
*
|
||||
* \param sample_freq_estimate The initial expected sample rate (in Hz).
|
||||
*
|
||||
**/
|
||||
void spdif_rx(streaming chanend c, in port p, clock clk, unsigned sample_freq_estimate);
|
||||
|
||||
/** Receive a sample from the S/PDIF component.
|
||||
*
|
||||
* This function receives a sample from the S/PDIF component. It is a
|
||||
* "select handler" so can be used within a select e.g.
|
||||
*
|
||||
\verbatim
|
||||
int32_t sample;
|
||||
size_t index;
|
||||
select {
|
||||
case spdif_rx_sample(c, sample, index):
|
||||
// use sample and index here...
|
||||
...
|
||||
break;
|
||||
...
|
||||
\endverbatim
|
||||
*
|
||||
* The case in this select will fire when the S/PDIF component has data ready.
|
||||
*
|
||||
* \param c chanend connected to the S/PDIF receiver component
|
||||
* \param sample This reference parameter gets set with the incoming
|
||||
* sample data
|
||||
* \param index This is the index of the same in the current frame
|
||||
* (i.e. 0 for left channel and 1 for right channel).
|
||||
*/
|
||||
#pragma select handler
|
||||
void spdif_rx_sample(streaming chanend c, int32_t &sample, size_t &index);
|
||||
|
||||
/** Shutdown the S/PDIF receiver component.
|
||||
*
|
||||
* This function shuts down the SPDIF RX component causing the call to
|
||||
* spdif_rx() to return.
|
||||
*
|
||||
* \param c chanend connected to the S/PDIF receiver component
|
||||
*/
|
||||
void spdif_rx_shutdown(streaming chanend c);
|
||||
|
||||
/** Checks the parity of a received S/PDIF sample
|
||||
*
|
||||
* \param sample Received sample to be checked
|
||||
*
|
||||
* \return Non-zero for error parity, otherwise 0
|
||||
*
|
||||
*/
|
||||
static inline int spdif_rx_check_parity(unsigned sample)
|
||||
{
|
||||
unsigned x = (sample>>4);
|
||||
crc32(x, 0, 1);
|
||||
return x & 1;
|
||||
}
|
||||
|
||||
/** S/PDIF transmit configure port function
|
||||
*
|
||||
* This function configures a port to be used by the SPDIF transmit
|
||||
* function.
|
||||
*
|
||||
* This function takes a delay for the clock that is to be passed into
|
||||
* the S/PDIF transmitter component. It sets the clock such that output data
|
||||
* is slightly delayed. This will work if I2S is clocked off the same clock
|
||||
* but ensures S/PDIF functions correctly.
|
||||
*
|
||||
* \param p the port that the S/PDIF component will use
|
||||
* \param clk the clock that the S/PDIF component will use
|
||||
* \param p_mclk The clock connected to the master clock frequency.
|
||||
* Usually this should be configured to be driven by
|
||||
* an incoming master system clock.
|
||||
* \param delay delay to uses to sync the SPDIF signal at the external
|
||||
* flip-flop
|
||||
*/
|
||||
void spdif_tx_port_config(out buffered port:32 p, clock clk, in port p_mclk, unsigned delay);
|
||||
|
||||
/** S/PDIF transmit function.
|
||||
*
|
||||
* This function provides an S/PDIF transmit component.
|
||||
* It is capable of 44100, 48000, 88200, 96000, and 192000 Hz sample
|
||||
* rates.
|
||||
*
|
||||
* The sample rate can be dynamically changes during the operation
|
||||
* of the component. Note that the first API call to this component
|
||||
* should be to reconfigure the sample rate (using the
|
||||
* spdif_tx_reconfigure_sample_rate() function).
|
||||
*
|
||||
* \param p_spdif The output port to transmit to
|
||||
* \param c chanend to connect to the application
|
||||
*/
|
||||
void spdif_tx(buffered out port:32 p_spdif, chanend c);
|
||||
|
||||
/** Reconfigure the S/PDIF tx component to a new sample rate.
|
||||
*
|
||||
* This function instructs the S/PDIF transmitter component to change
|
||||
* sample rate.
|
||||
*
|
||||
* \param c_spdif_tx chanend connected to the S/PDIF transmitter
|
||||
* \param sample_frequency The required new sample frequency in Hz.
|
||||
* \param master_clock_frequency The master_clock_frequency that the S/PDIF
|
||||
* transmitter is using
|
||||
*/
|
||||
void spdif_tx_reconfigure_sample_rate(chanend c_spdif_tx,
|
||||
unsigned sample_frequency,
|
||||
unsigned master_clock_frequency);
|
||||
|
||||
/** Output a sample pair to the S/PDIF transmitter component.
|
||||
*
|
||||
* This function will output a left channel and right channel sample to
|
||||
* the S/PDIF transmitter.
|
||||
*
|
||||
* \param c_spdif_tx chanend connected to the S/PDIF transmitter
|
||||
* \param lsample left sample to transmit
|
||||
* \param rsample right sample to transmit
|
||||
*/
|
||||
void spdif_tx_output(chanend c_spdif_tx, unsigned lsample, unsigned rsample);
|
||||
|
||||
/** Shutdown the S/PDIF transmitter component.
|
||||
*
|
||||
* This function shuts down the SPDIF Tx component causing the call to
|
||||
* spdif_tx() to return.
|
||||
*
|
||||
* \param c chanend connected to the S/PDIF transmitter component
|
||||
*/
|
||||
void spdif_tx_shutdown(chanend c);
|
||||
|
||||
#endif /* _SPDIF_H_ */
|
||||
7
lib_spdif/lib_spdif/lib_build_info.cmake
Normal file
7
lib_spdif/lib_spdif/lib_build_info.cmake
Normal file
@@ -0,0 +1,7 @@
|
||||
set(LIB_NAME lib_spdif)
|
||||
set(LIB_VERSION 6.1.0)
|
||||
set(LIB_INCLUDES api)
|
||||
set(LIB_COMPILER_FLAGS -O3)
|
||||
set(LIB_DEPENDENT_MODULES "")
|
||||
|
||||
XMOS_REGISTER_MODULE()
|
||||
15
lib_spdif/lib_spdif/module_build_info
Normal file
15
lib_spdif/lib_spdif/module_build_info
Normal file
@@ -0,0 +1,15 @@
|
||||
VERSION = 6.1.0
|
||||
|
||||
DEPENDENT_MODULES =
|
||||
|
||||
MODULE_XCC_FLAGS = $(XCC_FLAGS) \
|
||||
-O3
|
||||
|
||||
OPTIONAL_HEADERS +=
|
||||
|
||||
EXPORT_INCLUDE_DIRS = api \
|
||||
src
|
||||
|
||||
INCLUDE_DIRS = $(EXPORT_INCLUDE_DIRS)
|
||||
|
||||
SOURCE_DIRS = src
|
||||
242
lib_spdif/lib_spdif/src/SpdifReceive.xc
Normal file
242
lib_spdif/lib_spdif/src/SpdifReceive.xc
Normal file
@@ -0,0 +1,242 @@
|
||||
// Copyright 2023-2024 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
#include <xs1.h>
|
||||
#include <xclib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "spdif.h"
|
||||
|
||||
static inline int cls(int idata)
|
||||
{
|
||||
int x;
|
||||
#ifdef __XS3A__
|
||||
asm volatile("cls %0, %1" : "=r"(x) : "r"(idata));
|
||||
#else
|
||||
x = (clz(idata) + clz(~idata));
|
||||
#endif
|
||||
return x;
|
||||
}
|
||||
|
||||
static inline int xor4(int idata1, int idata2, int idata3, int idata4)
|
||||
{
|
||||
int x;
|
||||
|
||||
#ifdef __XS1B__
|
||||
/* For doc build only */
|
||||
x = idata1 ^ idata2 ^ idata3 ^ idata4;
|
||||
#else
|
||||
asm volatile("xor4 %0, %1, %2, %3, %4" : "=r"(x) : "r"(idata1), "r"(idata2), "r"(idata3), "r"(idata4));
|
||||
#endif
|
||||
return x;
|
||||
}
|
||||
|
||||
// Lookup table for error signal based on where the reference transition was.
|
||||
// Index can be max of 32 so need 33 element array.
|
||||
// Index 0 is never used.
|
||||
// To maximise timing margins, I actually need 0 error at input of 2.375 (2+3/8).
|
||||
// This implements ((input-2.375)*8) - so we now have a 29.3 fixed point number. The 0 error is now at an input of 2.375
|
||||
// We also apply an additional << 3 (*8) to save instructions later on in the PLL control loop.
|
||||
// Above an index of 6 we apply a max limit to the error to avoid port INs missing timing (this level of error only used during initial lock)
|
||||
const int error_lookup[33] = {-152,-88,-24,40,104,168,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232};
|
||||
|
||||
// 48k mask dehash lookup
|
||||
const char dehash_0x04040404_0xF[16] = {8, 9, 12, 13, 7, 6, 3, 2, 10, 11, 14, 15, 5, 4, 1, 0};
|
||||
|
||||
// 44.1k mask dehash lookup
|
||||
const char dehash_0x08040201_0xF[16] = {15, 7, 11, 3, 13, 5, 9, 1, 14, 6, 10, 2, 12, 4, 8, 0};
|
||||
|
||||
#pragma unsafe arrays
|
||||
static inline void spdif_rx_8UI(buffered in port:32 p, unsigned &t, unsigned &sample, unsigned &outword, unsigned &adder, unsigned &mask, unsigned *dehash)
|
||||
{
|
||||
asm volatile("in %0, res[%1]" : "=r"(sample) : "r"(p)); // Input data sample
|
||||
t += adder; // Add current adder value to the time for next input
|
||||
asm volatile("setpt res[%0], %1"::"r"(p),"r"(t>>16)); // Write the top 16 bits of that 16.16 fixed point time to the port
|
||||
unsigned crc = sample & mask; // Apply a mask to sample the data bits we want
|
||||
crc32(crc, 0xF, 0xF); // Use crc as a hash function into a unique 4 bit value based off value of sampled bits
|
||||
outword >>= 4; // Shift output word by 4 to make room for new data
|
||||
outword |= dehash[crc]; // OR the sampled data bits into the output word using a de-hash lookup table from the crc value
|
||||
}
|
||||
|
||||
#pragma unsafe arrays
|
||||
int spdif_rx_decode(streaming chanend c, buffered in port:32 p, unsigned sample_rate)
|
||||
{
|
||||
unsigned sample, raw_err;
|
||||
unsigned outword = 0;
|
||||
unsigned z_pre_sample = 0;
|
||||
unsigned unlock_cnt = 0;
|
||||
unsigned t;
|
||||
unsigned adder, mask, z_pre_len;
|
||||
unsigned dehash[16];
|
||||
unsigned pre_count = 0;
|
||||
unsigned char tmp; // used in exit function
|
||||
|
||||
if ((sample_rate % 11025) == 0) // 44.1 based rates
|
||||
{
|
||||
adder = 2321995; // ideal 44.1 (35.430 * 65536)
|
||||
z_pre_len = 9;
|
||||
mask = 0x08040201;
|
||||
for(int i=0;i<16;i++)
|
||||
dehash[i] = dehash_0x08040201_0xF[i] << 28;
|
||||
}
|
||||
else if ((sample_rate % 16000) == 0) // 48 based rates
|
||||
{
|
||||
adder = 2133333; // ideal 48.0 (32.552 * 65536)
|
||||
z_pre_len = 8;
|
||||
mask = 0x04040404;
|
||||
for(int i=0;i<16;i++)
|
||||
dehash[i] = dehash_0x04040404_0xF[i] << 28;
|
||||
}
|
||||
else // Sample rate not supported
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Start by locking local clock freq (adder) and phase (t) to input stream.
|
||||
|
||||
// Read the port counter and add a bit.
|
||||
p :> void @ t; // read port counter
|
||||
t+= 100;
|
||||
t <<= 16; // t is now in 16.16 fixed point format
|
||||
|
||||
// Run the loop for 512 INs, just running the PLL every time, no preamble check. This is because in this initial course lock phase we may have port times that are below nominal giving us even less time.
|
||||
// We will exit this phase in lock but maybe locked to the wrong edge. The adder value will be approx correct.
|
||||
for(int i = 0; i < 512;i++)
|
||||
{
|
||||
asm volatile("setpt res[%0], %1"::"r"(p),"r"(t>>16));
|
||||
asm volatile("in %0, res[%1]" : "=r"(sample) : "r"(p));
|
||||
raw_err = error_lookup[cls(sample<<9)];
|
||||
adder -= raw_err;
|
||||
t += adder - (raw_err<<9);
|
||||
}
|
||||
|
||||
// We then run loop again for a fixed time looking for preambles.
|
||||
// Check preambles are there, if not we add 2UI to port time and exit. This will bump the port time to the correct reference edge.
|
||||
for(int i = 0; i < 512;i++)
|
||||
{
|
||||
asm volatile("setpt res[%0], %1"::"r"(p),"r"(t>>16));
|
||||
if (cls(sample) > 9)
|
||||
pre_count++;
|
||||
asm volatile("in %0, res[%1]" : "=r"(sample) : "r"(p));
|
||||
raw_err = error_lookup[cls(sample<<9)];
|
||||
adder -= raw_err;
|
||||
t += adder - (raw_err<<9);
|
||||
}
|
||||
|
||||
if (pre_count < 16)
|
||||
t += (17<<15); // 8.5 bump if we were locked to wrong edge
|
||||
|
||||
// We then run loop again for a fixed time counting preambles.
|
||||
pre_count = 0;
|
||||
for(int i = 0; i < 512;i++)
|
||||
{
|
||||
asm volatile("setpt res[%0], %1"::"r"(p),"r"(t>>16));
|
||||
if (cls(sample) > 9)
|
||||
pre_count++;
|
||||
asm volatile("in %0, res[%1]" : "=r"(sample) : "r"(p));
|
||||
raw_err = error_lookup[cls(sample<<9)];
|
||||
adder -= raw_err;
|
||||
t += adder - (raw_err<<9);
|
||||
}
|
||||
|
||||
// Check preambles are there, if not we quit. We should have ~64 preambles in 512 input samples.
|
||||
if (pre_count < 60)
|
||||
return 0;
|
||||
|
||||
// Set the new port time ready for the first IN.
|
||||
asm volatile("setpt res[%0], %1"::"r"(p),"r"(t>>16));
|
||||
|
||||
// Now receive data
|
||||
while(unlock_cnt < 32)
|
||||
{
|
||||
spdif_rx_8UI(p, t, sample, outword, adder, mask, dehash);
|
||||
if (cls(sample) > 9) // Last three bits of old subframe and first "bit" of preamble.
|
||||
{
|
||||
outword = xor4(outword, (outword << 1), 0xFFFFFFFF, z_pre_sample); // This achieves the xor decode plus inverting the output in one step.
|
||||
outword <<= 1;
|
||||
c <: outword;
|
||||
|
||||
spdif_rx_8UI(p, t, sample, outword, adder, mask, dehash);
|
||||
z_pre_sample = sample;
|
||||
// Measure the position of reference edge and apply correction to PLL.
|
||||
unsigned ref_tran = cls(sample<<9);
|
||||
int raw_err = error_lookup[ref_tran];
|
||||
if (ref_tran > 5)
|
||||
unlock_cnt++;
|
||||
adder -= raw_err;
|
||||
t -= raw_err<<6;
|
||||
spdif_rx_8UI(p, t, sample, outword, adder, mask, dehash);
|
||||
if (cls(sample<<13) > 9) // Check pulse length of pulse leading up to reference transition. If too long our clock is too fast so add to error and eventually quit.
|
||||
unlock_cnt++;
|
||||
spdif_rx_8UI(p, t, sample, outword, adder, mask, dehash);
|
||||
spdif_rx_8UI(p, t, sample, outword, adder, mask, dehash);
|
||||
if (cls(z_pre_sample<<13) > z_pre_len)
|
||||
z_pre_sample = 2;
|
||||
else
|
||||
z_pre_sample = 0;
|
||||
spdif_rx_8UI(p, t, sample, outword, adder, mask, dehash);
|
||||
spdif_rx_8UI(p, t, sample, outword, adder, mask, dehash);
|
||||
|
||||
select
|
||||
{
|
||||
case sinct_byref(c, tmp):
|
||||
soutct(c, XS1_CT_END);
|
||||
unlock_cnt = 128; // Setting this value will cause exit of the while loop.
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
spdif_rx_8UI(p, t, sample, outword, adder, mask, dehash);
|
||||
}
|
||||
else // shoudn't get here in normal operation, means we've missed a preamble.
|
||||
unlock_cnt++;
|
||||
}
|
||||
|
||||
if (unlock_cnt == 128)
|
||||
return 1; // Return due to request from channel
|
||||
else
|
||||
return 0; // Return due to too many timing errors
|
||||
}
|
||||
|
||||
// This function checks the input signal is approximately the correct sample rate for the given mode/clock setting.
|
||||
int check_clock_div(buffered in port:32 p)
|
||||
{
|
||||
unsigned sample;
|
||||
unsigned max_pulse = 0;
|
||||
unsigned min_pulse = 1000;
|
||||
|
||||
// Flush the port
|
||||
p :> void;
|
||||
p :> void;
|
||||
|
||||
// Capture a large number of samples directly from the port and record the maximum pulse length seen.
|
||||
// Need enough samples to ensure we get a realistic 3UI max pulse which only happen in the preambles.
|
||||
// Only looking at leading pulse on each word which will be shorter than actual but due to async sampling
|
||||
// will eventually move into timing to correctly capture correct length.
|
||||
for(int i=0; i<5000;i++) // 5000 32 bit samples @ 100MHz takes 1.6ms
|
||||
{
|
||||
p :> sample;
|
||||
if (cls(sample) > max_pulse)
|
||||
{
|
||||
max_pulse = cls(sample);
|
||||
}
|
||||
|
||||
// Now find the minimum pulse width
|
||||
sample <<= cls(sample); // Shift off the top pulse (likely to not be a complete pulse)
|
||||
if (cls(sample) < min_pulse)
|
||||
{
|
||||
min_pulse = cls(sample);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the max_pulse is in expected range.
|
||||
// Shortest expected is 3UI @ 96k = 244ns nominal. Sampled at 20ns = 12 bits.
|
||||
// Longest expected is 3UI @ 88.2k = 266ns nominal but up to 300ns w/jitter.
|
||||
// Sampled at 20ns = 16 bits.
|
||||
// Note DC (all 0 or all 1s) will correctly fail (return 1) as max_pulse = 32.
|
||||
if ((max_pulse > 11) && (max_pulse < 17) && (min_pulse > 1) && (min_pulse < 7))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
376
lib_spdif/lib_spdif/src/SpdifTransmit.xc
Normal file
376
lib_spdif/lib_spdif/src/SpdifTransmit.xc
Normal file
@@ -0,0 +1,376 @@
|
||||
// Copyright 2011-2023 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
/**
|
||||
* @file SpditTransmit.xc
|
||||
* @brief S/PDIF line transmitter
|
||||
* @author XMOS
|
||||
*
|
||||
* Uses a master clock to output S/PDIF encoded samples.
|
||||
*/
|
||||
|
||||
#include <xs1.h>
|
||||
#include <xclib.h>
|
||||
#include "assert.h"
|
||||
#include "spdif.h"
|
||||
|
||||
/* Validity bit (x<<28) - the validity bit is set to 0 if the data is reliable and 1 if it is not.*/
|
||||
#define VALIDITY (0x00000000)
|
||||
|
||||
void spdif_tx_port_config(out buffered port:32 p, clock clk, in port p_mclk, unsigned delay)
|
||||
{
|
||||
assert(delay < 512);
|
||||
|
||||
/* Clock clock block from master-clock */
|
||||
configure_clock_src(clk, p_mclk);
|
||||
|
||||
/* Clock S/PDIF tx port from MClk */
|
||||
configure_out_port_no_ready(p, clk, 0);
|
||||
|
||||
/* Set delay to align SPDIF output to the clock at the external flop */
|
||||
set_clock_fall_delay(clk, delay);
|
||||
|
||||
/* Note, we so not start the clock to allow sharing of the clock-block */
|
||||
//start_clock(clk);
|
||||
}
|
||||
|
||||
/* Returns parity for a given word */
|
||||
static unsigned inline parity32(unsigned x)
|
||||
{
|
||||
crc32(x, 0, 1);
|
||||
return (x & 1);
|
||||
}
|
||||
|
||||
// Three preambles
|
||||
#define SPDIF_PREAMBLE_Z (0x17) // Block start & Sub-frame 1
|
||||
#define SPDIF_PREAMBLE_X (0x47) // Sub-frame 1
|
||||
#define SPDIF_PREAMBLE_Y (0x27) // Sub-frame 2
|
||||
|
||||
// This encodes 16 input data bits into 32 biphase mark bits.
|
||||
// 16 input bits must be in the LS 16 bits of the 32 bit input.
|
||||
// Serial stream progresses from LSB first to MSB last for input and output.
|
||||
// The previous biphase mark encoded bit is assumed to be 0, if 1, simply invert output.
|
||||
static inline unsigned biphase_encode(unsigned data_in)
|
||||
{
|
||||
unsigned poly = ~data_in << 16;
|
||||
unsigned residual = 0x0000FFFF;
|
||||
crcn(residual, 0, poly, 16);
|
||||
return zip(residual >> 1, ~residual, 0);
|
||||
}
|
||||
|
||||
// Takes the least significant 16 bits of inword, maps each input bit to 6 output bits to produce three 32 bit words which are output to the port.
|
||||
#pragma unsafe arrays
|
||||
static inline void zip_out_6(out buffered port:32 p, unsigned inword)
|
||||
{
|
||||
unsigned outword[3] = {0}; // Initialise our 3 output words to zero.
|
||||
for(int i=0; i<16; i++) // Loop over LS 16 bits
|
||||
{
|
||||
unsigned out_shift_cnt = i*6; // Tracking where we want to be in chunks of 6 bits
|
||||
unsigned out_shift_cnt_mod = out_shift_cnt % 32;
|
||||
unsigned out_shift_cnt_div = out_shift_cnt >> 5; // equiv to /32
|
||||
unsigned mask = 1 << i; // Create a single bit mask at the input bit we want to process
|
||||
if ((inword & mask) != 0) // Input bit is 1
|
||||
{
|
||||
outword[out_shift_cnt_div] |= (0x3F << out_shift_cnt_mod); // Set the 6 output bits at the correct position
|
||||
// Two special cases where the 6 output bits will cross a 32 bit word boundary.
|
||||
// Manually set the LS bits that cross over into the next output word.
|
||||
if (i == 5)
|
||||
outword[1] |= 0xF;
|
||||
else if (i == 10)
|
||||
outword[2] |= 0x3;
|
||||
}
|
||||
// Output words to port as soon as they are ready
|
||||
if (i == 5)
|
||||
p <: outword[0];
|
||||
else if (i == 10)
|
||||
p <: outword[1];
|
||||
}
|
||||
p <: outword[2];
|
||||
}
|
||||
|
||||
static inline void output_word(out buffered port:32 p, unsigned encoded_word, int divide)
|
||||
{
|
||||
switch(divide)
|
||||
{
|
||||
case 1:
|
||||
/* Highest sample freq supported by mclk freq, eg: 24 -> 192 */
|
||||
p <: encoded_word; // Output the encoded data to the port;
|
||||
break;
|
||||
case 2:
|
||||
/* E.g. 24 -> 96 */
|
||||
unsigned long long tmp;
|
||||
tmp = zip(encoded_word, encoded_word, 0); // Make a 64 bit word from two copies of 32 bit input word
|
||||
p <: (unsigned int) tmp; // Output LS 32 bits
|
||||
p <: (unsigned int) (tmp >> 32); // Output MS 32 bits
|
||||
break;
|
||||
case 4:
|
||||
/* E.g. 24MHz -> 48kHz */
|
||||
unsigned long long tmp, final;
|
||||
unsigned tmp_0, tmp_1;
|
||||
tmp = zip(encoded_word, encoded_word, 0); // Make a 64 bit word from two copies of 32 bit input word
|
||||
tmp_0 = (unsigned int) tmp; // LS 32 bits
|
||||
final = zip(tmp_0, tmp_0, 1); // Make a 64 bit word from two copies of 32 bit input word
|
||||
p <: (unsigned int) final;
|
||||
p <: (unsigned int) (final >> 32);
|
||||
tmp_1 = (unsigned int) (tmp >> 32); // MS 32 bits
|
||||
final = zip(tmp_1, tmp_1, 1); // Make a 64 bit word from two copies of 32 bit input word
|
||||
p <: (unsigned int) final;
|
||||
p <: (unsigned int) (final >> 32);
|
||||
break;
|
||||
case 6:
|
||||
/* E.g. 24.576MHz -> 32kHz */
|
||||
zip_out_6(p, encoded_word ); // Output LS 16 bits as 3*32bit words
|
||||
zip_out_6(p, (encoded_word >> 16)); // Output MS 16 bits as 3*32bit words
|
||||
break;
|
||||
default:
|
||||
/* Mclk does not support required sample freq - unreachable due to previous error checks */
|
||||
__builtin_unreachable();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma unsafe arrays
|
||||
static inline void subframe_tx(out buffered port:32 p, unsigned sample_in, int ctrl, unsigned char encoded_preamble, int divide)
|
||||
{
|
||||
static int lastbit = 0;
|
||||
unsigned word, sample, control, parity;
|
||||
sample = sample_in >> 4 & 0x0FFFFFF0; /* Mask and shift to be in the correct place in the Sub-frame */
|
||||
control = (ctrl & 1) << 30;
|
||||
parity = parity32(sample | control | VALIDITY) << 31;
|
||||
word = sample | control | parity | VALIDITY;
|
||||
|
||||
if(lastbit == 1)
|
||||
{
|
||||
encoded_preamble ^= 0xFF; // invert all bits of the encoded preamble
|
||||
}
|
||||
// Don't need to update lastbit here as due to pattern of preamble bits it is never changed.
|
||||
|
||||
word = word >> 4; // We've finished with the preamble
|
||||
|
||||
/* Next 12 bits of subframe word */
|
||||
unsigned encoded_word = biphase_encode(word & 0xFFF);
|
||||
if(lastbit == 1)
|
||||
{
|
||||
encoded_word = ~encoded_word; // invert all bits of the encoded word
|
||||
}
|
||||
encoded_word = (encoded_word << 8) | encoded_preamble;
|
||||
|
||||
// Now we do need to update lastbit to see if the last bit we're sending was 1 or 0.
|
||||
lastbit = encoded_word >> 31;
|
||||
output_word(p, encoded_word, divide);
|
||||
|
||||
word = word >> 12; // Shift the word down the 12 bits we've just output.
|
||||
|
||||
/* Remaining 16 bits of subframe word (we've shifted right by 4 and then 12 so only bottom 16 still remaining) */
|
||||
encoded_word = biphase_encode(word);
|
||||
if(lastbit == 1)
|
||||
{
|
||||
encoded_word = ~encoded_word; // invert all bits of the encoded word
|
||||
}
|
||||
|
||||
// Now we do need to update lastbit to see if the last bit we're sending was 1 or 0.
|
||||
lastbit = encoded_word >> 31;
|
||||
output_word(p, encoded_word, divide);
|
||||
}
|
||||
|
||||
void SpdifTransmit(out buffered port:32 p, chanend c_tx0, const int ctrl_left[2], const int ctrl_right[2], int divide)
|
||||
{
|
||||
unsigned sample_l, sample_r;
|
||||
|
||||
/* Check for new frequency */
|
||||
if(testct(c_tx0))
|
||||
{
|
||||
chkct(c_tx0, XS1_CT_END);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Get L/R samples */
|
||||
sample_l = inuint(c_tx0);
|
||||
sample_r = inuint(c_tx0);
|
||||
|
||||
#pragma unsafe arrays
|
||||
while (1)
|
||||
{
|
||||
int controlLeft = ctrl_left[0];
|
||||
int controlRight = ctrl_right[0];
|
||||
|
||||
for(int i = 0 ; i < 192; i++)
|
||||
{
|
||||
/* Sub-frame 1 */
|
||||
if(i == 0)
|
||||
{
|
||||
subframe_tx(p, sample_l, controlLeft, SPDIF_PREAMBLE_Z, divide); // Block start & Sub-frame 1
|
||||
}
|
||||
else
|
||||
{
|
||||
subframe_tx(p, sample_l, controlLeft, SPDIF_PREAMBLE_X, divide); // Sub-frame 1
|
||||
}
|
||||
|
||||
controlLeft >>=1;
|
||||
|
||||
/* Sub-frame 2 */
|
||||
subframe_tx(p, sample_r, controlRight, SPDIF_PREAMBLE_Y, divide);
|
||||
|
||||
controlRight >>=1;
|
||||
|
||||
/* Test for new frequency */
|
||||
if(testct(c_tx0))
|
||||
{
|
||||
chkct(c_tx0, XS1_CT_END);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Get new samples... */
|
||||
sample_l = inuint(c_tx0);
|
||||
sample_r = inuint(c_tx0);
|
||||
|
||||
if(i == 31)
|
||||
{
|
||||
controlLeft = ctrl_left[1];
|
||||
controlRight = ctrl_right[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SpdifTransmitError(chanend c_in)
|
||||
{
|
||||
while(1)
|
||||
{
|
||||
/* Keep swallowing samples until we get a sample frequency change */
|
||||
if (testct(c_in))
|
||||
{
|
||||
chkct(c_in, XS1_CT_END);
|
||||
return;
|
||||
}
|
||||
|
||||
inuint(c_in);
|
||||
inuint(c_in);
|
||||
}
|
||||
}
|
||||
|
||||
/* Defines for building channel status words */
|
||||
#define CHAN_STAT_L (0x00107A04)
|
||||
#define CHAN_STAT_R (0x00207A04)
|
||||
|
||||
#define CHAN_STAT_32000 (0x03000000)
|
||||
#define CHAN_STAT_44100 (0x00000000)
|
||||
#define CHAN_STAT_48000 (0x02000000)
|
||||
#define CHAN_STAT_88200 (0x08000000)
|
||||
#define CHAN_STAT_96000 (0x0A000000)
|
||||
#define CHAN_STAT_176400 (0x0C000000)
|
||||
#define CHAN_STAT_192000 (0x0E000000)
|
||||
|
||||
#define CHAN_STAT_WORD_2 (0x0000000B)
|
||||
|
||||
/* S/PDIF transmit thread */
|
||||
void spdif_tx(buffered out port:32 p, chanend c_in)
|
||||
{
|
||||
chkct(c_in, XS1_CT_END);
|
||||
while(1)
|
||||
{
|
||||
int chanStat_L[2], chanStat_R[2];
|
||||
unsigned divide;
|
||||
unsigned error = 0;
|
||||
|
||||
/* Check for shutdown */
|
||||
if (testct(c_in))
|
||||
{
|
||||
chkct(c_in, XS1_CT_END);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Receive sample frequency over channel (in Hz) */
|
||||
unsigned samFreq = inuint(c_in);
|
||||
|
||||
/* Receive master clock frequency over channel (in Hz) */
|
||||
unsigned mclkFreq = inuint(c_in);
|
||||
|
||||
/* Create channel status words based on sample freq */
|
||||
switch(samFreq)
|
||||
{
|
||||
case 32000:
|
||||
chanStat_L[0] = CHAN_STAT_L | CHAN_STAT_32000;
|
||||
chanStat_R[0] = CHAN_STAT_R | CHAN_STAT_32000;
|
||||
break;
|
||||
|
||||
case 44100:
|
||||
chanStat_L[0] = CHAN_STAT_L | CHAN_STAT_44100;
|
||||
chanStat_R[0] = CHAN_STAT_R | CHAN_STAT_44100;
|
||||
break;
|
||||
|
||||
case 48000:
|
||||
chanStat_L[0] = CHAN_STAT_L | CHAN_STAT_48000;
|
||||
chanStat_R[0] = CHAN_STAT_R | CHAN_STAT_48000;
|
||||
break;
|
||||
|
||||
case 88200:
|
||||
chanStat_L[0] = CHAN_STAT_L | CHAN_STAT_88200;
|
||||
chanStat_R[0] = CHAN_STAT_R | CHAN_STAT_88200;
|
||||
break;
|
||||
|
||||
case 96000:
|
||||
chanStat_L[0] = CHAN_STAT_L | CHAN_STAT_96000;
|
||||
chanStat_R[0] = CHAN_STAT_R | CHAN_STAT_96000;
|
||||
break;
|
||||
|
||||
case 176400:
|
||||
chanStat_L[0] = CHAN_STAT_L | CHAN_STAT_176400;
|
||||
chanStat_R[0] = CHAN_STAT_R | CHAN_STAT_176400;
|
||||
break;
|
||||
|
||||
case 192000:
|
||||
chanStat_L[0] = CHAN_STAT_L | CHAN_STAT_192000;
|
||||
chanStat_R[0] = CHAN_STAT_R | CHAN_STAT_192000;
|
||||
break;
|
||||
|
||||
default:
|
||||
error++;
|
||||
break;
|
||||
|
||||
}
|
||||
chanStat_L[1] = CHAN_STAT_WORD_2;
|
||||
chanStat_R[1] = CHAN_STAT_WORD_2;
|
||||
|
||||
/* Calculate required divide */
|
||||
divide = mclkFreq / (samFreq * 2 * 32 * 2);
|
||||
|
||||
if((divide != 1) && (divide != 2) && (divide != 4) && (divide != 6))
|
||||
error++;
|
||||
|
||||
if(error)
|
||||
SpdifTransmitError(c_in);
|
||||
else
|
||||
SpdifTransmit(p, c_in, chanStat_L, chanStat_R, divide);
|
||||
}
|
||||
}
|
||||
|
||||
void spdif_tx_reconfig_port(chanend c, out port p_spdif, const clock mclk)
|
||||
{
|
||||
out port * movable pp = &p_spdif;
|
||||
out buffered port:32 * movable pbuf = reconfigure_port(move(pp), out buffered port:32);
|
||||
/* Clock S/PDIF tx port from MClk */
|
||||
configure_out_port_no_ready(*pbuf, mclk, 0);
|
||||
spdif_tx(*pbuf, c);
|
||||
}
|
||||
|
||||
void spdif_tx_output(chanend c, unsigned l, unsigned r)
|
||||
{
|
||||
outuint(c, l);
|
||||
outuint(c, r);
|
||||
}
|
||||
|
||||
void spdif_tx_reconfigure_sample_rate(chanend c,
|
||||
unsigned sample_frequency,
|
||||
unsigned master_clock_frequency)
|
||||
{
|
||||
outct(c, XS1_CT_END);
|
||||
outuint(c, sample_frequency);
|
||||
outuint(c, master_clock_frequency);
|
||||
}
|
||||
|
||||
void spdif_tx_shutdown(chanend c)
|
||||
{
|
||||
outct(c, XS1_CT_END);
|
||||
outct(c, XS1_CT_END);
|
||||
}
|
||||
81
lib_spdif/lib_spdif/src/spdif_rx.xc
Normal file
81
lib_spdif/lib_spdif/src/spdif_rx.xc
Normal file
@@ -0,0 +1,81 @@
|
||||
// Copyright 2014-2023 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
#include <xs1.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <xclib.h>
|
||||
|
||||
#include "spdif.h"
|
||||
|
||||
void spdif_rx_sample(streaming chanend c, int32_t &sample, size_t &index)
|
||||
{
|
||||
uint32_t v;
|
||||
c :> v;
|
||||
index = (v & SPDIF_RX_PREAMBLE_MASK) == SPDIF_FRAME_Y ? 1 : 0;
|
||||
sample = SPDIF_RX_EXTRACT_SAMPLE(v);
|
||||
}
|
||||
|
||||
void spdif_rx_shutdown(streaming chanend c)
|
||||
{
|
||||
soutct(c, XS1_CT_END);
|
||||
|
||||
// Drain channel
|
||||
while(!stestct(c))
|
||||
c :> unsigned tmp;
|
||||
|
||||
sinct(c);
|
||||
}
|
||||
|
||||
int spdif_rx_decode(streaming chanend c, buffered in port:32 p, unsigned sample_rate);
|
||||
int check_clock_div(buffered in port:32 p);
|
||||
|
||||
void spdif_rx(streaming chanend c, in port p, clock clk, unsigned sample_freq_estimate)
|
||||
{
|
||||
unsigned sample_rate = sample_freq_estimate;
|
||||
int exit = 0;
|
||||
|
||||
in port * movable pp = &p;
|
||||
in buffered port:32 * movable p_buf = reconfigure_port(move(pp), in buffered port:32);
|
||||
|
||||
// Configure spdif rx port to be clocked from spdif_rx clock defined below.
|
||||
configure_in_port(*p_buf, clk);
|
||||
|
||||
while(1)
|
||||
{
|
||||
// Determine 100MHz clock divider
|
||||
unsigned clock_div = 96001/sample_rate;
|
||||
|
||||
// Stop clock so we can reconfigure it
|
||||
stop_clock(clk);
|
||||
|
||||
// Set the desired clock div
|
||||
configure_clock_ref(clk, clock_div);
|
||||
|
||||
// Start the clock block running. Port timer will be reset here.
|
||||
start_clock(clk);
|
||||
|
||||
// Check our clock div value is correct
|
||||
if (check_clock_div(*p_buf) == 0)
|
||||
exit = spdif_rx_decode(c, *p_buf, sample_rate);
|
||||
|
||||
if(exit)
|
||||
break;
|
||||
|
||||
// Get next sample rate from current sample rate.
|
||||
switch(sample_rate)
|
||||
{
|
||||
case 32000: sample_rate = 44100; break;
|
||||
case 44100: sample_rate = 48000; break;
|
||||
case 48000: sample_rate = 88200; break;
|
||||
case 88200: sample_rate = 96000; break;
|
||||
case 96000: sample_rate = 176400; break;
|
||||
case 176400: sample_rate = 192000; break;
|
||||
case 192000: sample_rate = 32000; break;
|
||||
default: sample_rate = 48000; break;
|
||||
}
|
||||
}
|
||||
|
||||
// Set pointers and ownership back to original state if SpdifReceive() exits
|
||||
pp = reconfigure_port(move(p_buf), in port);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user