Revision 1551
Added by markw about 23 hours ago
| 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
|
||
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.