Index: atari_chips/pokeyv2/dc_blocker.vhdl
===================================================================
--- atari_chips/pokeyv2/dc_blocker.vhdl	(nonexistent)
+++ atari_chips/pokeyv2/dc_blocker.vhdl	(revision 1551)
@@ -0,0 +1,120 @@
+---------------------------------------------------------------------------
+-- (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;
Index: atari_chips/pokeyv2/mixer.vhdl
===================================================================
--- atari_chips/pokeyv2/mixer.vhdl	(revision 1550)
+++ atari_chips/pokeyv2/mixer.vhdl	(revision 1551)
@@ -57,14 +57,7 @@
 		
 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;
@@ -87,14 +80,10 @@
 	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);
 	
@@ -107,7 +96,7 @@
 	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);
@@ -136,11 +125,8 @@
 		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;
@@ -151,8 +137,7 @@
 		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;
@@ -172,18 +157,12 @@
 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;
@@ -190,8 +169,7 @@
 		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');
@@ -242,43 +220,25 @@
 				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);
@@ -293,12 +253,12 @@
 
 		-- 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
Index: atari_chips/pokeyv2/pokeymax.vhd
===================================================================
--- atari_chips/pokeyv2/pokeymax.vhd	(revision 1550)
+++ atari_chips/pokeyv2/pokeymax.vhd	(revision 1551)
@@ -247,6 +247,7 @@
 	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);
 	
@@ -300,6 +301,7 @@
 	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);
@@ -503,6 +505,7 @@
 	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);
@@ -848,6 +851,47 @@
 		 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
@@ -1088,8 +1132,8 @@
 -- 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;
@@ -1212,6 +1256,27 @@
 		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;		
 	
 --------------------------------------------------------
@@ -1903,18 +1968,18 @@
 	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,
 
@@ -2266,6 +2331,17 @@
 
 	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
