---------------------------------------------------------------------------
-- (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;
