--------------------------------------------------------------------------- -- (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; USE IEEE.STD_LOGIC_UNSIGNED.ALL; use IEEE.STD_LOGIC_MISC.all; LIBRARY work; -- audio only, no need for io part ENTITY PSG_top IS PORT ( CLK : in std_logic; RESET_N : in std_logic; ENABLE : in std_logic; ENVELOPE32 : in std_logic := '1'; -- 0=16 step,1=32 step ADDR : in std_logic_vector(3 downto 0); --TODO: Handy to address this way, but could use the original crappy way if people prefer! WRITE_ENABLE : in std_logic; DI : in std_logic_vector(7 downto 0); DO : out std_logic_vector(7 downto 0); IOA_IN : in std_logic_vector(7 downto 0) := (others=>'0'); IOB_IN : in std_logic_vector(7 downto 0) := (others=>'0'); IOA_OUT : out std_logic_vector(7 downto 0); IOB_OUT : out std_logic_vector(7 downto 0); IOA_OE : out std_logic; IOB_OE : out std_logic; channel_a_vol : out std_logic_vector(4 downto 0); channel_b_vol : out std_logic_vector(4 downto 0); channel_c_vol : out std_logic_vector(4 downto 0); channel_changed : out std_logic ); END PSG_top; ARCHITECTURE vhdl OF PSG_top IS signal period_channel_a_reg : std_logic_vector(11 downto 0); signal period_channel_a_next : std_logic_vector(11 downto 0); signal period_channel_b_reg : std_logic_vector(11 downto 0); signal period_channel_b_next : std_logic_vector(11 downto 0); signal period_channel_c_reg : std_logic_vector(11 downto 0); signal period_channel_c_next : std_logic_vector(11 downto 0); signal period_noise_reg : std_logic_vector(4 downto 0); signal period_noise_next : std_logic_vector(4 downto 0); signal vol_channel_a_reg : std_logic_vector(4 downto 0); signal vol_channel_a_next : std_logic_vector(4 downto 0); signal vol_channel_b_reg : std_logic_vector(4 downto 0); signal vol_channel_b_next : std_logic_vector(4 downto 0); signal vol_channel_c_reg : std_logic_vector(4 downto 0); signal vol_channel_c_next : std_logic_vector(4 downto 0); signal period_envelope_reg : std_logic_vector(15 downto 0); signal period_envelope_next : std_logic_vector(15 downto 0); signal shape_envelope_reg : std_logic_vector(3 downto 0); signal shape_envelope_next : std_logic_vector(3 downto 0); signal mixer_noise_reg : std_logic_vector(2 downto 0); signal mixer_noise_next : std_logic_vector(2 downto 0); signal mixer_tone_reg : std_logic_vector(2 downto 0); signal mixer_tone_next : std_logic_vector(2 downto 0); signal io_output_reg : std_logic_vector(1 downto 0); signal io_output_next : std_logic_vector(1 downto 0); signal ioa_reg : std_logic_vector(7 downto 0); signal ioa_next : std_logic_vector(7 downto 0); signal iob_reg : std_logic_vector(7 downto 0); signal iob_next : std_logic_vector(7 downto 0); signal addr_decoded : std_logic_vector(15 downto 0); signal core_tick : std_logic; signal core_tick_half : std_logic; signal channel_a_tick : std_logic; signal channel_b_tick : std_logic; signal channel_c_tick : std_logic; signal noise_tick : std_logic; signal noise_val : std_logic; signal channel_a_val : std_logic; signal channel_b_val : std_logic; signal channel_c_val : std_logic; signal channel_a_changed : std_logic; signal channel_b_changed : std_logic; signal channel_c_changed : std_logic; signal envelope_reg : std_logic_vector(4 downto 0); signal envelope_count_reset : std_logic; BEGIN process(clk,reset_n) begin if (reset_n='0') then period_channel_a_reg <= (others=>'0'); period_channel_b_reg <= (others=>'0'); period_channel_c_reg <= (others=>'0'); period_noise_reg <= (others=>'0'); vol_channel_a_reg <= (others=>'0'); vol_channel_b_reg <= (others=>'0'); vol_channel_c_reg <= (others=>'0'); period_envelope_reg <= (others=>'0'); shape_envelope_reg <= (others=>'0'); mixer_noise_reg <= (others=>'0'); mixer_tone_reg <= (others=>'0'); io_output_reg <= (others=>'0'); ioa_reg <= (others=>'0'); iob_reg <= (others=>'0'); elsif (clk'event and clk='1') then period_channel_a_reg <= period_channel_a_next; period_channel_b_reg <= period_channel_b_next; period_channel_c_reg <= period_channel_c_next; period_noise_reg <= period_noise_next; vol_channel_a_reg <= vol_channel_a_next; vol_channel_b_reg <= vol_channel_b_next; vol_channel_c_reg <= vol_channel_c_next; period_envelope_reg <= period_envelope_next; shape_envelope_reg <= shape_envelope_next; mixer_noise_reg <= mixer_noise_next; mixer_tone_reg <= mixer_tone_next; io_output_reg <= io_output_next; ioa_reg <= ioa_next; iob_reg <= iob_next; end if; end process; decode_addr1 : entity work.complete_address_decoder generic map(width=>4) port map (addr_in=>ADDR(3 downto 0), addr_decoded=>addr_decoded); process(addr_decoded,write_enable,di, period_channel_a_reg,period_channel_b_reg,period_channel_c_reg, period_noise_reg, vol_channel_a_reg,vol_channel_b_reg,vol_channel_c_reg, period_envelope_reg, shape_envelope_reg, mixer_noise_reg, mixer_tone_reg, ioa_reg, iob_reg, io_output_reg ) begin period_channel_a_next <= period_channel_a_reg; period_channel_b_next <= period_channel_b_reg; period_channel_c_next <= period_channel_c_reg; period_noise_next <= period_noise_reg; vol_channel_a_next <= vol_channel_a_reg; vol_channel_b_next <= vol_channel_b_reg; vol_channel_c_next <= vol_channel_c_reg; period_envelope_next <= period_envelope_reg; shape_envelope_next <= shape_envelope_reg; mixer_noise_next <= mixer_noise_reg; mixer_tone_next <= mixer_tone_reg; io_output_next <= io_output_reg; ioa_next <= ioa_reg; iob_next <= iob_reg; envelope_count_reset <= '0'; if (write_enable='1') then if (addr_decoded(0)='1') then period_channel_a_next(7 downto 0) <= di; end if; if (addr_decoded(1)='1') then period_channel_a_next(11 downto 8) <= di(3 downto 0); end if; if (addr_decoded(2)='1') then period_channel_b_next(7 downto 0) <= di; end if; if (addr_decoded(3)='1') then period_channel_b_next(11 downto 8) <= di(3 downto 0); end if; if (addr_decoded(4)='1') then period_channel_c_next(7 downto 0) <= di; end if; if (addr_decoded(5)='1') then period_channel_c_next(11 downto 8) <= di(3 downto 0); end if; if (addr_decoded(6)='1') then period_noise_next <= di(4 downto 0); end if; if (addr_decoded(7)='1') then io_output_next <= di(7 downto 6); mixer_noise_next <= di(5 downto 3); mixer_tone_next <= di(2 downto 0); end if; if (addr_decoded(8)='1') then vol_channel_a_next <= di(4 downto 0); end if; if (addr_decoded(9)='1') then vol_channel_b_next <= di(4 downto 0); end if; if (addr_decoded(10)='1') then vol_channel_c_next <= di(4 downto 0); end if; if (addr_decoded(11)='1') then period_envelope_next(7 downto 0) <= di; end if; if (addr_decoded(12)='1') then period_envelope_next(15 downto 8) <= di; end if; if (addr_decoded(13)='1') then shape_envelope_next <= di(3 downto 0); envelope_count_reset <= '1'; end if; if (addr_decoded(14)='1') then ioa_next <= di; end if; if (addr_decoded(15)='1') then iob_next <= di; end if; end if; end process; process(addr_decoded, period_channel_a_reg,period_channel_b_reg,period_channel_c_reg, period_noise_reg, vol_channel_a_reg,vol_channel_b_reg,vol_channel_c_reg, period_envelope_reg, shape_envelope_reg, mixer_noise_reg, mixer_tone_reg, ioa_in, iob_in, io_output_reg ) begin do <= (others=>'0'); if (addr_decoded(0)='1') then do <= period_channel_a_reg(7 downto 0); end if; if (addr_decoded(1)='1') then do(3 downto 0) <= period_channel_a_reg(11 downto 8); end if; if (addr_decoded(2)='1') then do <= period_channel_b_reg(7 downto 0); end if; if (addr_decoded(3)='1') then do(3 downto 0) <= period_channel_b_reg(11 downto 8); end if; if (addr_decoded(4)='1') then do <= period_channel_c_reg(7 downto 0); end if; if (addr_decoded(5)='1') then do(3 downto 0) <= period_channel_c_reg(11 downto 8); end if; if (addr_decoded(6)='1') then do(4 downto 0) <= period_noise_reg; end if; if (addr_decoded(7)='1') then do(7 downto 6) <= io_output_reg; do(5 downto 3) <= mixer_noise_reg; do(2 downto 0) <= mixer_tone_reg; end if; if (addr_decoded(8)='1') then do(4 downto 0) <= vol_channel_a_reg; end if; if (addr_decoded(9)='1') then do(4 downto 0) <= vol_channel_b_reg; end if; if (addr_decoded(10)='1') then do(4 downto 0) <= vol_channel_c_reg; end if; if (addr_decoded(11)='1') then do <= period_envelope_reg(7 downto 0); end if; if (addr_decoded(12)='1') then do <= period_envelope_reg(15 downto 8); end if; if (addr_decoded(13)='1') then do(3 downto 0) <= shape_envelope_reg; end if; if (addr_decoded(14)='1') then do <= ioa_in; end if; if (addr_decoded(15)='1') then do <= iob_in; end if; end process; -- initial divide by 8 core_ticker : entity work.PSG_freqdiv GENERIC MAP ( bits => 4 ) PORT MAP ( CLK => clk, RESET_N => reset_n, ENABLE => enable, BIT_OUT => core_tick, THRESHOLD => "1000" ); -- channels A-C, frequency divider channel_a_ticker : entity work.PSG_freqdiv GENERIC MAP ( bits => 12 ) PORT MAP ( CLK => clk, RESET_N => reset_n, ENABLE => core_tick, BIT_OUT => channel_a_tick, THRESHOLD => unsigned(period_channel_a_reg) ); channel_b_ticker : entity work.PSG_freqdiv GENERIC MAP ( bits => 12 ) PORT MAP ( CLK => clk, RESET_N => reset_n, ENABLE => core_tick, BIT_OUT => channel_b_tick, THRESHOLD => unsigned(period_channel_b_reg) ); channel_c_ticker : entity work.PSG_freqdiv GENERIC MAP ( bits => 12 ) PORT MAP ( CLK => clk, RESET_N => reset_n, ENABLE => core_tick, BIT_OUT => channel_c_tick, THRESHOLD => unsigned(period_channel_c_reg) ); -- noise --17-bit LFSR with taps at bits 17 and 14 --ref:https://listengine.tuxfamily.org/lists.tuxfamily.org/hatari-devel/2012/09/msg00045.html -- noise freq->noise_tick->noise_val noise_preticker : entity work.PSG_freqdiv GENERIC MAP ( bits => 2 ) PORT MAP ( CLK => clk, RESET_N => reset_n, ENABLE => core_tick, BIT_OUT => core_tick_half, THRESHOLD => "10" ); noise_ticker : entity work.PSG_freqdiv GENERIC MAP ( bits => 5 ) PORT MAP ( CLK => clk, RESET_N => reset_n, ENABLE => core_tick_half, BIT_OUT => noise_tick, THRESHOLD => unsigned(period_noise_reg) ); noise : entity work.PSG_noise PORT MAP ( CLK => clk, RESET_N => reset_n, ENABLE => noise_tick, TICK => noise_tick, BIT_OUT => noise_val ); -- mix noise and channel mix_a : entity work.PSG_mixer PORT MAP ( CLK => clk, RESET_N => reset_n, ENABLE => enable, NOISE => noise_val, CHANNEL => channel_a_tick, NOISE_OFF => mixer_noise_reg(0), TONE_OFF => mixer_tone_reg(0), BIT_OUT => channel_a_val ); mix_b : entity work.PSG_mixer PORT MAP ( CLK => clk, RESET_N => reset_n, ENABLE => enable, NOISE => noise_val, CHANNEL => channel_b_tick, NOISE_OFF => mixer_noise_reg(1), TONE_OFF => mixer_tone_reg(1), BIT_OUT => channel_b_val ); mix_c : entity work.PSG_mixer PORT MAP ( CLK => clk, RESET_N => reset_n, ENABLE => enable, NOISE => noise_val, CHANNEL => channel_c_tick, NOISE_OFF => mixer_noise_reg(2), TONE_OFF => mixer_tone_reg(2), BIT_OUT => channel_c_val ); -- envelope envelope : entity work.PSG_envelope PORT MAP ( CLK => clk, RESET_N => reset_n, ENABLE => core_tick, STEP32 => envelope32, COUNT_RESET => envelope_count_reset, SHAPE => shape_envelope_reg, PERIOD => period_envelope_reg, ENVELOPE => envelope_reg ); -- volume vol_a : entity work.PSG_volume PORT MAP ( CLK => clk, RESET_N => reset_n, ENABLE => enable, CHANNEL => channel_a_val, FIXED => vol_channel_a_reg, ENVELOPE => envelope_reg, VOL_OUT => channel_a_vol, CHANGED => channel_a_changed ); vol_b : entity work.PSG_volume PORT MAP ( CLK => clk, RESET_N => reset_n, ENABLE => enable, CHANNEL => channel_b_val, FIXED => vol_channel_b_reg, ENVELOPE => envelope_reg, VOL_OUT => channel_b_vol, CHANGED => channel_b_changed ); vol_c : entity work.PSG_volume PORT MAP ( CLK => clk, RESET_N => reset_n, ENABLE => enable, CHANNEL => channel_c_val, FIXED => vol_channel_c_reg, ENVELOPE => envelope_reg, VOL_OUT => channel_c_vol, CHANGED => channel_c_changed ); -- outputs IOA_OUT <= ioa_reg; IOB_OUT <= iob_reg; IOA_OE <= io_output_reg(0); IOB_OE <= io_output_reg(1); channel_changed <= channel_a_changed or channel_b_changed or channel_c_changed; end vhdl;