Project

General

Profile

« Previous | Next » 

Revision 1551

Added by markw about 1 month 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