|
---------------------------------------------------------------------------
|
|
-- (c) 2020 mark watson
|
|
-- I am happy for anyone to use this for non-commercial use.
|
|
-- If my vhdl files are used commercially or otherwise sold,
|
|
-- please contact me for explicit permission at scrameta (gmail).
|
|
-- This applies for source and binary form and derived works.
|
|
---------------------------------------------------------------------------
|
|
|
|
LIBRARY ieee;
|
|
USE ieee.std_logic_1164.all;
|
|
use ieee.numeric_std.all;
|
|
use IEEE.STD_LOGIC_MISC.all;
|
|
use work.AudioTypes.all;
|
|
|
|
LIBRARY work;
|
|
|
|
ENTITY mixer IS
|
|
PORT
|
|
(
|
|
CLK : IN STD_LOGIC;
|
|
RESET_N : IN STD_LOGIC;
|
|
|
|
ENABLE_CYCLE : IN STD_LOGIC;
|
|
|
|
DETECT_RIGHT : IN STD_LOGIC;
|
|
POST_DIVIDE : IN STD_LOGIC_VECTOR(7 downto 0);
|
|
FANCY_ENABLE : IN STD_LOGIC;
|
|
B_CH0_EN : IN STD_LOGIC_VECTOR(3 downto 0);
|
|
B_CH1_EN : IN STD_LOGIC_VECTOR(3 downto 0);
|
|
|
|
L_CH0 : IN SIGNED(15 downto 0);
|
|
L_CH1 : IN SIGNED(15 downto 0);
|
|
L_CH2 : IN SIGNED(15 downto 0);
|
|
L_CH3 : IN SIGNED(15 downto 0);
|
|
L_CH4 : IN SIGNED(15 downto 0);
|
|
R_CH0 : IN SIGNED(15 downto 0);
|
|
R_CH1 : IN SIGNED(15 downto 0);
|
|
R_CH2 : IN SIGNED(15 downto 0);
|
|
R_CH3 : IN SIGNED(15 downto 0);
|
|
R_CH4 : IN SIGNED(15 downto 0);
|
|
B_CH0 : IN SIGNED(15 downto 0);
|
|
B_CH1 : IN SIGNED(15 downto 0);
|
|
|
|
AUDIO_0_SIGNED : out signed(15 downto 0);
|
|
AUDIO_1_SIGNED : out signed(15 downto 0);
|
|
AUDIO_2_SIGNED : out signed(15 downto 0);
|
|
AUDIO_3_SIGNED : out signed(15 downto 0)
|
|
);
|
|
END mixer;
|
|
|
|
ARCHITECTURE vhdl OF mixer IS
|
|
|
|
-- DC blocker constants
|
|
constant DC_EXTRA_BITS : integer := 4;
|
|
constant DC_ACC_WIDTH : integer := 16 + DC_EXTRA_BITS; -- 20 bits
|
|
constant DC_K : integer := 10;
|
|
|
|
subtype dc_acc_t is signed(DC_ACC_WIDTH-1 downto 0);
|
|
type dc_arr_t is array (0 to 3) of dc_acc_t;
|
|
|
|
-- DETECT RIGHT PLAYING
|
|
signal RIGHT_PLAYING_RECENTLY : std_logic;
|
|
signal RIGHT_NEXT : std_logic;
|
|
signal RIGHT_REG : std_logic;
|
|
signal RIGHT_PLAYING_COUNT_NEXT : unsigned(23 downto 0);
|
|
signal RIGHT_PLAYING_COUNT_REG : unsigned(23 downto 0);
|
|
|
|
-- sums
|
|
signal audio0_reg : signed(15 downto 0);
|
|
signal audio0_next : signed(15 downto 0);
|
|
signal audio1_reg : signed(15 downto 0);
|
|
signal audio1_next : signed(15 downto 0);
|
|
signal audio2_reg : signed(15 downto 0);
|
|
signal audio2_next : signed(15 downto 0);
|
|
signal audio3_reg : signed(15 downto 0);
|
|
signal audio3_next : signed(15 downto 0);
|
|
|
|
signal acc_reg : signed(19 downto 0);
|
|
signal acc_next : signed(19 downto 0);
|
|
|
|
-- DC blocker per-channel state
|
|
signal dc_reg : dc_arr_t;
|
|
signal dc_next : dc_arr_t;
|
|
|
|
-- Pipeline register: holds dc-corrected divided value between state_dc and state_clear
|
|
signal dc_corrected_reg : signed(19 downto 0);
|
|
signal dc_corrected_next : signed(19 downto 0);
|
|
|
|
signal out_ch_reg : std_logic_vector(1 downto 0);
|
|
signal out_ch_next : std_logic_vector(1 downto 0);
|
|
|
|
signal state_reg : unsigned(3 downto 0);
|
|
signal state_next : unsigned(3 downto 0);
|
|
constant state_CH0 : unsigned(3 downto 0) := "0000";
|
|
constant state_CH1 : unsigned(3 downto 0) := "0001";
|
|
constant state_CH2 : unsigned(3 downto 0) := "0010";
|
|
constant state_CH3 : unsigned(3 downto 0) := "0011";
|
|
constant state_CH4 : unsigned(3 downto 0) := "0100";
|
|
constant state_BCH0 : unsigned(3 downto 0) := "0101";
|
|
constant state_BCH1 : unsigned(3 downto 0) := "0110";
|
|
constant state_dc : unsigned(3 downto 0) := "0111"; -- divide + dc block
|
|
constant state_clear : unsigned(3 downto 0) := "1000"; -- saturate + write output
|
|
|
|
signal channelsel : std_logic_vector(3 downto 0);
|
|
signal include_in_output : std_logic_vector(3 downto 0);
|
|
signal left_on_right : std_logic;
|
|
|
|
signal volume : signed(15 downto 0);
|
|
signal saturated : signed(15 downto 0);
|
|
|
|
signal write : std_logic;
|
|
|
|
BEGIN
|
|
-- DETECT IF RIGHT CHANNEL PLAYING
|
|
-- TODO: into another entity
|
|
process(clk,reset_n)
|
|
begin
|
|
if (reset_n='0') then
|
|
RIGHT_REG <= '0';
|
|
RIGHT_PLAYING_COUNT_REG <= (others=>'0');
|
|
audio0_reg <= (others=>'0');
|
|
audio1_reg <= (others=>'0');
|
|
audio2_reg <= (others=>'0');
|
|
audio3_reg <= (others=>'0');
|
|
acc_reg <= (others=>'0');
|
|
out_ch_reg <= (others=>'0');
|
|
dc_corrected_reg <= (others=>'0');
|
|
state_reg <= state_CH0;
|
|
for i in 0 to 3 loop
|
|
dc_reg(i) <= (others=>'0');
|
|
end loop;
|
|
elsif (clk'event and clk='1') then
|
|
RIGHT_REG <= RIGHT_NEXT;
|
|
RIGHT_PLAYING_COUNT_REG <= RIGHT_PLAYING_COUNT_NEXT;
|
|
audio0_reg <= audio0_next;
|
|
audio1_reg <= audio1_next;
|
|
audio2_reg <= audio2_next;
|
|
audio3_reg <= audio3_next;
|
|
acc_reg <= acc_next;
|
|
out_ch_reg <= out_ch_next;
|
|
dc_reg <= dc_next;
|
|
dc_corrected_reg <= dc_corrected_next;
|
|
state_reg <= state_next;
|
|
end if;
|
|
end process;
|
|
|
|
process(RIGHT_NEXT,RIGHT_REG,ENABLE_CYCLE,RIGHT_PLAYING_RECENTLY,RIGHT_PLAYING_COUNT_REG)
|
|
begin
|
|
RIGHT_PLAYING_COUNT_NEXT <= RIGHT_PLAYING_COUNT_REG;
|
|
|
|
if (ENABLE_CYCLE='1' and RIGHT_PLAYING_RECENTLY='1') then
|
|
RIGHT_PLAYING_COUNT_NEXT <= RIGHT_PLAYING_COUNT_REG-1;
|
|
end if;
|
|
|
|
if (RIGHT_NEXT/=RIGHT_REG) then
|
|
RIGHT_PLAYING_COUNT_NEXT <= (others=>'1');
|
|
end if;
|
|
end process;
|
|
RIGHT_PLAYING_RECENTLY <= or_reduce(std_logic_vector(RIGHT_PLAYING_COUNT_REG));
|
|
|
|
|
|
process(state_reg,RIGHT_REG,out_ch_reg,acc_reg,volume,dc_reg,dc_corrected_reg,
|
|
POST_DIVIDE,SATURATED,include_in_output)
|
|
variable postdivide : std_logic_vector(1 downto 0);
|
|
variable presaturate : signed(19 downto 0);
|
|
variable addAcc : std_logic;
|
|
variable clearAcc : std_logic;
|
|
-- DC blocker datapath variables
|
|
variable ch_idx : integer range 0 to 3;
|
|
variable x_ext : dc_acc_t;
|
|
variable dc_cur : dc_acc_t;
|
|
variable err : dc_acc_t;
|
|
variable adj : dc_acc_t;
|
|
variable dc_new_v : dc_acc_t;
|
|
variable y_new : dc_acc_t;
|
|
begin
|
|
state_next <= state_reg;
|
|
out_ch_next <= out_ch_reg;
|
|
acc_next <= acc_reg;
|
|
RIGHT_NEXT <= RIGHT_REG;
|
|
dc_next <= dc_reg;
|
|
dc_corrected_next <= dc_corrected_reg;
|
|
|
|
write <= '0';
|
|
channelsel <= (others=>'0');
|
|
saturated <= (others=>'0');
|
|
addAcc := '0';
|
|
clearAcc := '0';
|
|
postdivide := "00";
|
|
|
|
case out_ch_reg is
|
|
when "00" =>
|
|
postdivide := POST_DIVIDE(1 downto 0);
|
|
addAcc := include_in_output(0);
|
|
when "01" =>
|
|
postdivide := POST_DIVIDE(3 downto 2);
|
|
addAcc := include_in_output(1);
|
|
when "10" =>
|
|
postdivide := POST_DIVIDE(5 downto 4);
|
|
addAcc := include_in_output(2);
|
|
when "11" =>
|
|
postdivide := POST_DIVIDE(7 downto 6);
|
|
addAcc := include_in_output(3);
|
|
when others =>
|
|
end case;
|
|
|
|
case state_reg is
|
|
when state_CH0 =>
|
|
channelsel <= x"0";
|
|
state_next <= state_CH1;
|
|
when state_CH1 =>
|
|
channelsel <= x"1";
|
|
state_next <= state_CH2;
|
|
when state_CH2 =>
|
|
channelsel <= x"2";
|
|
state_next <= state_CH3;
|
|
when state_CH3 =>
|
|
channelsel <= x"3";
|
|
state_next <= state_CH4;
|
|
when state_CH4 =>
|
|
channelsel <= x"4";
|
|
state_next <= state_BCH0;
|
|
when state_BCH0 =>
|
|
channelsel <= x"6";
|
|
state_next <= state_BCH1;
|
|
-- NEEDS DOING WITHOUT BCH* mixed, since those plays on all channels!!
|
|
RIGHT_NEXT <= (xor_reduce(std_logic_vector(acc_reg)) and out_ch_reg(0)) or (RIGHT_REG and not(out_ch_reg(0)));
|
|
when state_BCH1 =>
|
|
channelsel <= x"7";
|
|
state_next <= state_dc;
|
|
|
|
when state_dc =>
|
|
-- Divide accumulator and run dc blocker.
|
|
-- Result registered into dc_corrected_reg, accumulator cleared.
|
|
-- Critical path: shift + subtract + shift_right(K) + add + subtract
|
|
case postdivide is
|
|
when "00" => presaturate := resize(acc_reg(19 downto 0), 20);
|
|
when "01" => presaturate := resize(acc_reg(19 downto 1), 20);
|
|
when "10" => presaturate := resize(acc_reg(19 downto 2), 20);
|
|
when "11" => presaturate := resize(acc_reg(19 downto 3), 20);
|
|
when others =>
|
|
end case;
|
|
|
|
ch_idx := to_integer(unsigned(out_ch_reg));
|
|
x_ext := resize(presaturate, DC_ACC_WIDTH);
|
|
dc_cur := dc_reg(ch_idx);
|
|
err := x_ext - dc_cur;
|
|
adj := shift_right(err, DC_K);
|
|
dc_new_v := dc_cur + adj;
|
|
y_new := x_ext - dc_new_v;
|
|
|
|
dc_next(ch_idx) <= dc_new_v;
|
|
dc_corrected_next <= resize(y_new, 20);
|
|
clearAcc := '1';
|
|
state_next <= state_clear;
|
|
|
|
when state_clear =>
|
|
-- Saturate the registered dc-corrected value and write to output.
|
|
-- Critical path: just the saturation check + mux
|
|
write <= '1';
|
|
out_ch_next <= std_logic_vector(unsigned(out_ch_reg)+1);
|
|
state_next <= state_CH0;
|
|
|
|
when others =>
|
|
state_next <= state_CH0;
|
|
end case;
|
|
|
|
channelsel(3) <= out_ch_reg(0);
|
|
|
|
-- Saturation reads from the pipeline register, so only the
|
|
-- saturation check itself is on the state_clear critical path
|
|
if dc_corrected_reg(19 downto 15) /= "00000" and
|
|
dc_corrected_reg(19 downto 15) /= "11111" then
|
|
saturated(14 downto 0) <= (others => not dc_corrected_reg(19));
|
|
saturated(15) <= dc_corrected_reg(19);
|
|
else
|
|
saturated <= dc_corrected_reg(15 downto 0);
|
|
end if;
|
|
|
|
-- Accumulator update: clear takes priority over add
|
|
if clearAcc = '1' then
|
|
acc_next <= (others=>'0');
|
|
elsif addAcc = '1' then
|
|
acc_next <= acc_reg + resize(volume, 20);
|
|
end if;
|
|
|
|
end process;
|
|
|
|
process(state_reg,channelsel,
|
|
L_CH0,L_CH1,L_CH2,L_CH3,L_CH4,
|
|
R_CH0,R_CH1,R_CH2,R_CH3,R_CH4,
|
|
B_CH0,B_CH1,
|
|
B_CH0_EN,B_CH1_EN
|
|
)
|
|
begin
|
|
volume <= (others=>'0');
|
|
--left
|
|
include_in_output(0) <= not(channelsel(3));
|
|
include_in_output(2) <= not(channelsel(3));
|
|
--right
|
|
include_in_output(1) <= channelsel(3);
|
|
include_in_output(3) <= channelsel(3);
|
|
case channelsel is
|
|
when x"0" =>
|
|
volume <= L_CH0;
|
|
when x"1" =>
|
|
volume <= L_CH1;
|
|
when x"2" =>
|
|
volume <= L_CH2;
|
|
when x"3" =>
|
|
volume <= L_CH3;
|
|
when x"4" =>
|
|
volume <= L_CH4;
|
|
when x"8" =>
|
|
volume <= R_CH0;
|
|
when x"9" =>
|
|
volume <= R_CH1;
|
|
when x"a" =>
|
|
volume <= R_CH2;
|
|
when x"b" =>
|
|
volume <= R_CH3;
|
|
when x"c" =>
|
|
volume <= R_CH4;
|
|
when x"6"|x"e" =>
|
|
include_in_output <= B_CH0_EN;
|
|
volume <= B_CH0;
|
|
when x"7"|x"f" =>
|
|
include_in_output <= B_CH1_EN;
|
|
volume <= B_CH1;
|
|
when others =>
|
|
end case;
|
|
end process;
|
|
|
|
left_on_right <= not(FANCY_ENABLE) or (not(RIGHT_PLAYING_RECENTLY) AND DETECT_RIGHT);
|
|
|
|
process(write,saturated,out_ch_reg,left_on_right,audio0_reg,audio1_reg,audio2_reg,audio3_reg)
|
|
variable out_ch_adj : std_logic_vector(2 downto 0);
|
|
variable wr : std_logic_vector(3 downto 0);
|
|
begin
|
|
audio0_next <= audio0_reg;
|
|
audio1_next <= audio1_reg;
|
|
audio2_next <= audio2_reg;
|
|
audio3_next <= audio3_reg;
|
|
|
|
out_ch_adj(1 downto 0) := out_ch_reg;
|
|
out_ch_adj(2) := left_on_right;
|
|
|
|
wr := (others=>'0');
|
|
case out_ch_adj is
|
|
when "000" =>
|
|
wr(0) := write;
|
|
when "001" =>
|
|
wr(1) := write;
|
|
when "010" =>
|
|
wr(2) := write;
|
|
when "011" =>
|
|
wr(3) := write;
|
|
when "100" =>
|
|
wr(0) := write;
|
|
wr(1) := write;
|
|
when "110" =>
|
|
wr(2) := write;
|
|
wr(3) := write;
|
|
when others =>
|
|
-- 101 -> write to right, dropped since we are playing ONLY left on right
|
|
-- 111 -> write to right, dropped since we are playing ONLY left on right
|
|
-- Deliberate! We accumulate right still for the right detect logic but do not output it
|
|
end case;
|
|
|
|
if (wr(0)='1') then
|
|
audio0_next <= saturated;
|
|
end if;
|
|
if (wr(1)='1') then
|
|
audio1_next <= saturated;
|
|
end if;
|
|
if (wr(2)='1') then
|
|
audio2_next <= saturated;
|
|
end if;
|
|
if (wr(3)='1') then
|
|
audio3_next <= saturated;
|
|
end if;
|
|
end process;
|
|
|
|
-- output
|
|
AUDIO_0_SIGNED <= audio0_reg;
|
|
AUDIO_1_SIGNED <= audio1_reg;
|
|
AUDIO_2_SIGNED <= audio2_reg;
|
|
AUDIO_3_SIGNED <= audio3_reg;
|
|
end vhdl;
|