Project

General

Profile

« Previous | Next » 

Revision 1551

Added by markw about 11 hours ago

Move dc blocker to be per unsigned channel. Works better for recording and now everything is signed in priciple with no dc offset for the mixer.

View differences:

atari_chips/pokeyv2/dc_blocker.vhdl
---------------------------------------------------------------------------
-- (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;
-- Generic unsigned-input to signed-output DC blocker.
-- AUDIO_IN is treated as offset-binary unsigned audio:
-- 0 -> most negative signed value
-- 2**(BITS-1) -> zero
-- 2**BITS - 1 -> most positive signed value
--
-- Filter:
-- y = x - dc_old
-- dc = dc_old + (y / 2**K)
--
-- AUDIO_OUT is registered and saturated to BITS bits.
ENTITY dc_blocker IS
GENERIC
(
BITS : positive := 16;
EXTRA_BITS : positive := 4;
K : natural := 10
);
PORT
(
CLK : IN std_logic;
RESET_N : IN std_logic;
ENABLE_CYCLE : IN std_logic;
AUDIO_IN : IN unsigned(BITS-1 downto 0);
AUDIO_OUT : OUT signed(BITS-1 downto 0)
);
END dc_blocker;
ARCHITECTURE vhdl OF dc_blocker IS
constant ACC_WIDTH : positive := BITS + EXTRA_BITS;
subtype acc_t is signed(ACC_WIDTH-1 downto 0);
function midpoint return acc_t is
variable r : acc_t := (others => '0');
begin
r(BITS-1) := '1';
return r;
end function;
function saturate_to_bits(v : acc_t) return signed is
variable r : signed(BITS-1 downto 0);
variable overflow : boolean := false;
variable max_val : signed(BITS-1 downto 0) := (others => '1');
variable min_val : signed(BITS-1 downto 0) := (others => '0');
begin
max_val(BITS-1) := '0';
min_val(BITS-1) := '1';
-- A value fits into BITS signed bits when all bits above BITS-1
-- match the sign bit that will remain after truncation.
for i in BITS to ACC_WIDTH-1 loop
if v(i) /= v(BITS-1) then
overflow := true;
end if;
end loop;
if overflow then
if v(ACC_WIDTH-1) = '0' then
r := max_val;
else
r := min_val;
end if;
else
r := v(BITS-1 downto 0);
end if;
return r;
end function;
constant MIDPOINT_VALUE : acc_t := midpoint;
signal dc_reg : acc_t;
signal dc_next : acc_t;
signal audio_out_reg : signed(BITS-1 downto 0);
signal audio_out_next : signed(BITS-1 downto 0);
BEGIN
process(AUDIO_IN, ENABLE_CYCLE, dc_reg)
variable x_ext : acc_t;
variable err : acc_t;
variable adj : acc_t;
begin
x_ext := signed(resize(AUDIO_IN, ACC_WIDTH)) - MIDPOINT_VALUE;
err := x_ext - dc_reg;
adj := shift_right(err, K);
dc_next <= dc_reg;
audio_out_next <= saturate_to_bits(err);
if ENABLE_CYCLE = '1' then
dc_next <= dc_reg + adj;
end if;
end process;
process(CLK, RESET_N)
begin
if RESET_N = '0' then
dc_reg <= (others => '0');
audio_out_reg <= (others => '0');
elsif CLK'event and CLK = '1' then
dc_reg <= dc_next;
audio_out_reg <= audio_out_next;
end if;
end process;
AUDIO_OUT <= audio_out_reg;
END vhdl;
atari_chips/pokeyv2/mixer.vhdl
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 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 divided value between state_divide and state_clear
signal divided_reg : signed(19 downto 0);
signal divided_next : signed(19 downto 0);
-- 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);
......
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_divide : unsigned(3 downto 0) := "0111"; -- divide
constant state_clear : unsigned(3 downto 0) := "1000"; -- saturate + write output
signal channelsel : std_logic_vector(3 downto 0);
......
audio3_reg <= (others=>'0');
acc_reg <= (others=>'0');
out_ch_reg <= (others=>'0');
dc_corrected_reg <= (others=>'0');
divided_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_SNAP_REG <= RIGHT_SNAP_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;
divided_reg <= divided_next;
state_reg <= state_next;
end if;
end process;
......
RIGHT_PLAYING_RECENTLY <= or_reduce(std_logic_vector(RIGHT_PLAYING_COUNT_REG));
process(state_reg,RIGHT_REG,RIGHT_SNAP_REG,RIGHT_SNAP_NEXT,out_ch_reg,acc_reg,volume,dc_reg,dc_corrected_reg,
process(state_reg,RIGHT_REG,RIGHT_SNAP_REG,RIGHT_SNAP_NEXT,out_ch_reg,acc_reg,volume,divided_reg,
POST_DIVIDE,SATURATED,include_in_output,enable_cycle,mute_channel)
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;
begin
state_next <= state_reg;
out_ch_next <= out_ch_reg;
acc_next <= acc_reg;
RIGHT_NEXT <= RIGHT_REG;
RIGHT_SNAP_NEXT <= RIGHT_SNAP_REG;
dc_next <= dc_reg;
dc_corrected_next <= dc_corrected_reg;
divided_next <= divided_reg;
write <= '0';
channelsel <= (others=>'0');
......
end if;
when state_BCH1 =>
channelsel <= x"7";
state_next <= state_dc;
state_next <= state_divide;
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
when state_divide =>
-- Divide accumulator only. DC removal has been extracted to dc_blocker.
-- Result registered into divided_reg, accumulator cleared.
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 =>
when others => presaturate := acc_reg;
end case;
ch_idx := to_integer(unsigned(out_ch_reg));
x_ext := resize(presaturate, DC_ACC_WIDTH);
dc_cur := dc_reg(ch_idx);
divided_next <= presaturate;
clearAcc := '1';
state_next <= state_clear;
-- Cheap DC blocker:
-- y = x - dc_old
-- dc = dc_old + (y / 2**DC_K)
--
-- The previous version output x - dc_new, which costs an
-- extra subtractor and differs only by y/2**DC_K.
err := x_ext - dc_cur;
if ENABLE_CYCLE = '1' then
adj := shift_right(err, DC_K);
dc_next(ch_idx) <= dc_cur + adj;
end if;
dc_corrected_next <= resize(err, 20);
clearAcc := '1';
state_next <= state_clear;
when state_clear =>
-- Saturate the registered dc-corrected value and write to output.
-- Saturate the registered divided 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);
......
-- 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);
if divided_reg(19 downto 15) /= "00000" and
divided_reg(19 downto 15) /= "11111" then
saturated(14 downto 0) <= (others => not divided_reg(19));
saturated(15) <= divided_reg(19);
else
saturated <= dc_corrected_reg(15 downto 0);
saturated <= divided_reg(15 downto 0);
end if;
-- Accumulator update: clear takes priority over add
atari_chips/pokeyv2/pokeymax.vhd
signal DEVICE_ADDR : std_logic_vector(3 downto 0);
signal POKEY_AUDIO_UNSIGNED : UNSIGNED_AUDIO_TYPE(3 downto 0);
signal POKEY_AUDIO_SIGNED : SIGNED_AUDIO_TYPE(3 downto 0);
signal AUDIO_MIXED_SIGNED : SIGNED_AUDIO_TYPE(3 downto 0);
......
signal PSG_ENABLE_1Mhz : std_logic;
signal PSG_ENABLE : std_logic;
signal PSG_AUDIO_UNSIGNED : UNSIGNED_AUDIO_TYPE(1 downto 0);
signal PSG_AUDIO_SIGNED : SIGNED_AUDIO_TYPE(1 downto 0);
signal PSG_CHANNEL : PSG_CHANNEL_TYPE(5 downto 0);
signal PSG_CHANGED : std_logic_vector(1 downto 0);
......
signal fir_data_ready :std_logic;
signal SIO_AUDIO_UNSIGNED : unsigned(15 downto 0);
signal SIO_AUDIO_SIGNED : signed(15 downto 0);
-- paddles
signal PADDLE_ADJ : std_logic_vector(7 downto 0);
......
PROFILE_DATA => flash_do_slow(15 downto 0)
);
pokey1_dc_blocker : entity work.dc_blocker
PORT MAP
(
CLK => CLK,
RESET_N => RESET_N,
ENABLE_CYCLE => ENABLE_CYCLE,
AUDIO_IN => POKEY_AUDIO_UNSIGNED(0),
AUDIO_OUT => POKEY_AUDIO_SIGNED(0)
);
pokey2_dc_blocker : entity work.dc_blocker
PORT MAP
(
CLK => CLK,
RESET_N => RESET_N,
ENABLE_CYCLE => ENABLE_CYCLE,
AUDIO_IN => POKEY_AUDIO_UNSIGNED(1),
AUDIO_OUT => POKEY_AUDIO_SIGNED(1)
);
pokey3_dc_blocker : entity work.dc_blocker
PORT MAP
(
CLK => CLK,
RESET_N => RESET_N,
ENABLE_CYCLE => ENABLE_CYCLE,
AUDIO_IN => POKEY_AUDIO_UNSIGNED(2),
AUDIO_OUT => POKEY_AUDIO_SIGNED(2)
);
pokey4_dc_blocker : entity work.dc_blocker
PORT MAP
(
CLK => CLK,
RESET_N => RESET_N,
ENABLE_CYCLE => ENABLE_CYCLE,
AUDIO_IN => POKEY_AUDIO_UNSIGNED(3),
AUDIO_OUT => POKEY_AUDIO_SIGNED(3)
);
flash_off : if enable_flash=0 generate
shared_pokey_mixer : entity work.pokey_mixer
port map
......
-- PSG
--------------------------------------------------------
psg_off : if enable_psg=0 generate
PSG_AUDIO_UNSIGNED(0) <= to_unsigned(0,16);
PSG_AUDIO_UNSIGNED(1) <= to_unsigned(0,16);
PSG_AUDIO_SIGNED(0) <= to_signed(0,16);
PSG_AUDIO_SIGNED(1) <= to_signed(0,16);
PSG_DO(0) <= (others=>'0');
PSG_DO(1) <= (others=>'0');
end generate psg_off;
......
PROFILE_DATA => flash_do_slow(15 downto 0)
);
psg1_dc_blocker : entity work.dc_blocker
PORT MAP
(
CLK => CLK,
RESET_N => RESET_N,
ENABLE_CYCLE => ENABLE_CYCLE,
AUDIO_IN => PSG_AUDIO_UNSIGNED(0),
AUDIO_OUT => PSG_AUDIO_SIGNED(0)
);
psg2_dc_blocker : entity work.dc_blocker
PORT MAP
(
CLK => CLK,
RESET_N => RESET_N,
ENABLE_CYCLE => ENABLE_CYCLE,
AUDIO_IN => PSG_AUDIO_UNSIGNED(1),
AUDIO_OUT => PSG_AUDIO_SIGNED(1)
);
end generate psg_on;
--------------------------------------------------------
......
B_CH0_EN => GTIA_ENABLE_REG,
B_CH1_EN => "1100",
L_CH0 => unsigned_to_signed(POKEY_AUDIO_UNSIGNED(0)),
R_CH0 => unsigned_to_signed(POKEY_AUDIO_UNSIGNED(1)),
L_CH1 => unsigned_to_signed(POKEY_AUDIO_UNSIGNED(2)),
R_CH1 => unsigned_to_signed(POKEY_AUDIO_UNSIGNED(3)),
L_CH0 => POKEY_AUDIO_SIGNED(0),
R_CH0 => POKEY_AUDIO_SIGNED(1),
L_CH1 => POKEY_AUDIO_SIGNED(2),
R_CH1 => POKEY_AUDIO_SIGNED(3),
L_CH2 => SAMPLE_AUDIO_SIGNED(0),
R_CH2 => SAMPLE_AUDIO_SIGNED(1),
L_CH3 => SID_AUDIO_SIGNED(0),
R_CH3 => SID_AUDIO_SIGNED(1),
L_CH4 => unsigned_to_signed(PSG_AUDIO_UNSIGNED(0)),
R_CH4 => unsigned_to_signed(PSG_AUDIO_UNSIGNED(1)),
L_CH4 => PSG_AUDIO_SIGNED(0),
R_CH4 => PSG_AUDIO_SIGNED(1),
B_CH0 => GTIA_AUDIO_SIGNED,
B_CH1 => unsigned_to_signed(SIO_AUDIO_UNSIGNED),
B_CH1 => SIO_AUDIO_SIGNED,
MUTE_CHANNEL => mixer_mute,
......
SIO_AUDIO_UNSIGNED <= unsigned(not(adc_use_reg(15))&adc_use_reg(14 downto 0));
sio_audio_dc_blocker : entity work.dc_blocker
PORT MAP
(
CLK => CLK,
RESET_N => RESET_N,
ENABLE_CYCLE => ENABLE_CYCLE,
AUDIO_IN => SIO_AUDIO_UNSIGNED,
AUDIO_OUT => SIO_AUDIO_SIGNED
);
process(adc_reg,adc_output,adc_valid,ADC_VOLUME_REG)
variable adc_shrunk : signed(19 downto 0);
begin

Also available in: Unified diff