Project

General

Profile

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


(5-5/7)