This is the VHDL-logic to transmit 8 individual channels via a single digital transmission line. UltraNet uses an AES3-like connection that contains some Behringer specific channel-status-data. For longer transmission-lines a differential transceiver should be used.
Important side-note: even the VHDL-block will take 24-bit audio, UltraNet uses the least-significant two bits to transmit an information for left/right-channel. So effective its only a 22-bit audio-connection:
VHDL
-- 8-channel UltraNet-Transmitter
-- Christian Noeding, christian@noeding-online.de
-- https://chrisdevblog.com | https://github.com/xn--nding-jua
--
-- Based on 2-channel SP/DIF Transmitter by Danny Witberg from ackspace.nl SP/DIF_transmitter_project
-- Kudos to Samuel Tugler (https://blog.thestaticturtle.fr) for his reverse-engineering-work on UltraNet
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
entity ultranet_tx is
generic(
FRAME_COUNTER_RESET : std_logic_vector(8 downto 0) := "101111111";
AES3_PREAMBLE_X : std_logic_vector(7 downto 0) := "10010011";
AES3_PREAMBLE_Y : std_logic_vector(7 downto 0) := "10010110";
AES3_PREAMBLE_Z : std_logic_vector(7 downto 0) := "10011100"
);
port
(
bit_clock : in std_logic; -- 24.576 MHz
ch1 : in std_logic_vector(23 downto 0);
ch2 : in std_logic_vector(23 downto 0);
ch3 : in std_logic_vector(23 downto 0);
ch4 : in std_logic_vector(23 downto 0);
ch5 : in std_logic_vector(23 downto 0);
ch6 : in std_logic_vector(23 downto 0);
ch7 : in std_logic_vector(23 downto 0);
ch8 : in std_logic_vector(23 downto 0);
ultranet_out : out std_logic
);
end entity;
architecture behavioral of ultranet_tx is
-- Setup bits for UltraNet
-- valid-bit: '0' if audio sample word is suitable for conversion to an analogue audio signal. Set to '1' for UltraNet
-- user-status-bit: not used in UltraNet
-- channel-status-bit: according to AES/EBU specification for each channel 24 bytes are transmitted.
constant valid : std_logic := '1';
constant user_status : std_logic_vector(383 downto 0) := (others => '0');
constant channel_status : std_logic_vector(383 downto 0) :=
"00000000" & "00000000" & "00000000" & "00000000" & "00000000" & "00000000" & "00000000" & "00000000" &
"11000000" & "11110011" & "00000000" & "00000000" & "00000000" & "00000000" & "00000000" & "00000000" &
"00000000" & "00000000" & "00000000" & "00000000" & "00000000" & "00000000" & "00000000" & "00000000" &
"00000000" & "00000000" & "00000000" & "00000000" & "00000000" & "00000000" & "00000000" & "00000000" &
"00000000" & "00000000" & "00000000" & "00000000" & "00000000" & "00000000" & "00000000" & "00000000" &
"00000000" & "00000000" & "00000000" & "00000000" & "00000000" & "00000000" & "00000000" & "00000000";
-- counter signals
signal bit_counter : std_logic_vector(5 downto 0) := (others => '0');
signal frame_counter : std_logic_vector(8 downto 0) := (others => '0');
signal channel_counter : integer range 0 to 7;
-- temporary signals
signal parity : std_logic;
signal data_in_buffer : std_logic_vector(23 downto 0);
signal data_out_buffer : std_logic_vector(7 downto 0);
signal data_biphase : std_logic := '0';
signal user_status_shift : std_logic_vector(383 downto 0);
signal channel_status_shift : std_logic_vector(383 downto 0);
begin
bit_clock_counter : process (bit_clock)
begin
if bit_clock'event and bit_clock = '1' then
bit_counter <= bit_counter + 1;
end if;
end process bit_clock_counter;
data_latch : process (bit_clock)
begin
if bit_clock'event and bit_clock = '1' then
parity <= data_in_buffer(23) xor data_in_buffer(22) xor data_in_buffer(21) xor data_in_buffer(20) xor data_in_buffer(19) xor data_in_buffer(18) xor data_in_buffer(17) xor data_in_buffer(16) xor data_in_buffer(15) xor data_in_buffer(14) xor data_in_buffer(13) xor data_in_buffer(12) xor data_in_buffer(11) xor data_in_buffer(10) xor data_in_buffer(9) xor data_in_buffer(8) xor data_in_buffer(7) xor data_in_buffer(6) xor data_in_buffer(5) xor data_in_buffer(4) xor data_in_buffer(3) xor data_in_buffer(2) xor data_in_buffer(1) xor data_in_buffer(0) xor valid xor user_status_shift(383) xor channel_status_shift(383);
if bit_counter = 3 then
-- We are near the end of the preamble, load the sound data in the buffer
-- UltraNet seems to use only 22 bits for audio and the first two LSB for
-- identifying channel-pairs
if channel_counter = 0 then
data_in_buffer <= ch1(23 downto 2) & "00";
elsif channel_counter = 1 then
data_in_buffer <= ch2(23 downto 2) & "00";
elsif channel_counter = 2 then
data_in_buffer <= ch3(23 downto 2) & "01";
elsif channel_counter = 3 then
data_in_buffer <= ch4(23 downto 2) & "01";
elsif channel_counter = 4 then
data_in_buffer <= ch5(23 downto 2) & "10";
elsif channel_counter = 5 then
data_in_buffer <= ch6(23 downto 2) & "10";
elsif channel_counter = 6 then
data_in_buffer <= ch7(23 downto 2) & "11";
else
data_in_buffer <= ch8(23 downto 2) & "11";
end if;
end if;
if bit_counter = 63 then
-- We are at the 32th bit (2x due to biphase) which means the end of a frame
-- Check if this is the last frame in the audio block
if frame_counter = FRAME_COUNTER_RESET then
-- Yes, reset the frame counter
frame_counter <= (others => '0');
else
-- Nope, increment the frame counter
frame_counter <= frame_counter + 1;
end if;
end if;
end if;
end process data_latch;
data_output : process (bit_clock)
begin
-- On new bit clock pulse
if bit_clock'event and bit_clock = '1' then
if bit_counter = 63 then
-- We are at the 32th bit of the frame (2x due to biphase) which means the end of a frame
-- Check if this is the last frame in the audio block
if frame_counter = FRAME_COUNTER_RESET then
-- Next frame will be the first of the new audio block, load the Z preamble
channel_counter <= 0; -- reset channel-counter
user_status_shift <= user_status;
channel_status_shift <= channel_status;
data_out_buffer <= AES3_PREAMBLE_Z;
else
-- Next frame is NOT the first of the audio block
-- Check if the frame is even/odd (generally attributed to left/right)
if frame_counter(0) = '1' then
-- Next frame is even, load the X preamble
data_out_buffer <= AES3_PREAMBLE_X ;
else
-- Next frame is odd, load the Y preamble
data_out_buffer <= AES3_PREAMBLE_Y;
end if;
-- Shift the channel status and user by one to the left
user_status_shift <= user_status_shift(382 downto 0) & '0';
channel_status_shift <= channel_status_shift(382 downto 0) & '0';
-- increment or reset channel-counter
if (channel_counter < 7) then
channel_counter <= channel_counter + 1;
else
channel_counter <= 0;
end if;
end if;
else
if bit_counter(2 downto 0) = "111" then -- load new part of data into buffer
case bit_counter(5 downto 3) is
when "000" =>
data_out_buffer <= '1' & data_in_buffer(0) & '1' & data_in_buffer(1) & '1' & data_in_buffer(2) & '1' & data_in_buffer(3);
when "001" =>
data_out_buffer <= '1' & data_in_buffer(4) & '1' & data_in_buffer(5) & '1' & data_in_buffer(6) & '1' & data_in_buffer(7);
when "010" =>
data_out_buffer <= '1' & data_in_buffer(8) & '1' & data_in_buffer(9) & '1' & data_in_buffer(10) & '1' & data_in_buffer(11);
when "011" =>
data_out_buffer <= '1' & data_in_buffer(12) & '1' & data_in_buffer(13) & '1' & data_in_buffer(14) & '1' & data_in_buffer(15);
when "100" =>
data_out_buffer <= '1' & data_in_buffer(16) & '1' & data_in_buffer(17) & '1' & data_in_buffer(18) & '1' & data_in_buffer(19);
when "101" =>
data_out_buffer <= '1' & data_in_buffer(20) & '1' & data_in_buffer(21) & '1' & data_in_buffer(22) & '1' & data_in_buffer(23);
when "110" =>
data_out_buffer <= '1' & valid & '1' & user_status_shift(383) & '1' & channel_status_shift(383) & '1' & parity;
when others =>
end case;
else
data_out_buffer <= data_out_buffer(6 downto 0) & '0';
end if;
end if;
end if;
end process data_output;
biphaser : process (bit_clock)
begin
if bit_clock'event and bit_clock = '1' then
if data_out_buffer(data_out_buffer'left) = '1' then
data_biphase <= not data_biphase;
end if;
end if;
end process biphaser;
ultranet_out <= data_biphase;
end behavioral;
