I added an implementation of the TMS9919 sound chip. I basically just based it on the information available on the TI Tech Pages and then checked on the Classic99 implementation for the implementation of the noise generator. I don't know if this fully works, but at least TI Invaders played the correctly sounding effects. Very nice.
I did time how long it took to create and debug this: about two and half hours.
There is a very simple sigma-delta DAC. In my implementation the input data word is 8 bits and the logic frequency is 100MHz. The DAC is a classic implementation (I copied it from another source, the scramble game implementation, but this really is a text book version):
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity dac is
generic (
msbi_g : integer := 7
);
port (
clk_i : in std_logic;
res_n_i : in std_logic;
dac_i : in std_logic_vector(msbi_g downto 0);
dac_o : out std_logic
);
end dac;
architecture rtl of dac is
signal sig_in : unsigned(msbi_g+2 downto 0);
begin
seq: process (clk_i, res_n_i)
begin
if res_n_i = '0' then
sig_in <= to_unsigned(2**(msbi_g+1), sig_in'length);
dac_o <= '0';
elsif rising_edge(clk_i) then
sig_in <= sig_in + unsigned(sig_in(msbi_g+2) & sig_in(msbi_g+2) & dac_i);
dac_o <= sig_in(msbi_g+2);
end if;
end process seq;
end rtl;
Syntax highlighting again does not support VHDL.
And below is the implementation of the TMS9919 ( the main process block):
process(clk, reset)
variable k : std_logic;
begin
if reset = '1' then
latch_high <= (others => '0');
tone1_att <= "1111"; -- off
tone2_att <= "1111"; -- off
tone3_att <= "1111"; -- off
noise_att <= "1111"; -- off
master_divider <= 0;
add_value <= (others => '0');
add_flag <= '0';
elsif rising_edge(clk) then
if we='1' then
-- data write
if data_in(7) = '1' then
latch_high <= data_in(6 downto 0); -- store for later re-use
case data_in(6 downto 4) is
when "000" => tone1_div_val(3 downto 0) <= data_in(3 downto 0);
when "001" => tone1_att <= data_in(3 downto 0);
when "010" => tone2_div_val(3 downto 0) <= data_in(3 downto 0);
when "011" => tone2_att <= data_in(3 downto 0);
when "100" => tone3_div_val(3 downto 0) <= data_in(3 downto 0);
when "101" => tone3_att <= data_in(3 downto 0);
when "110" => noise_div_val <= data_in(3 downto 0);
noise_lfsr <= x"0001"; -- initialize noise generator
when "111" => noise_att <= data_in(3 downto 0);
when others =>
end case;
else
-- Write with MSB set to zero. Use latched register value.
case latch_high(6 downto 4) is
when "000" => tone1_div_val(9 downto 4) <= data_in(5 downto 0);
when "010" => tone2_div_val(9 downto 4) <= data_in(5 downto 0);
when "100" => tone3_div_val(9 downto 4) <= data_in(5 downto 0);
when others =>
end case;
end if;
end if;
-- Ok. Now handle the actual sound generators.
-- The input freuency on the TI-99/4A is 3.58MHz which is divided by 32, this is 111875Hz.
-- Our clock is 100MHz. As the first approximation we will divide 100MHz by 894.
-- That gives a clock of 111857Hz which is good enough.
-- After checking that actually yields half of the desired frequency. So let's go with 447.
master_divider <= master_divider + 1;
if master_divider >= 446 then
master_divider <= 0;
tone1_counter <= std_logic_vector(to_unsigned(to_integer(unsigned(tone1_counter)) - 1, tone1_counter'length));
tone2_counter <= std_logic_vector(to_unsigned(to_integer(unsigned(tone2_counter)) - 1, tone2_counter'length));
tone3_counter <= std_logic_vector(to_unsigned(to_integer(unsigned(tone3_counter)) - 1, tone3_counter'length));
noise_counter <= std_logic_vector(to_unsigned(to_integer(unsigned(noise_counter)) - 1, noise_counter'length));
if unsigned(tone1_counter) = 0 then
tone1_out <= not tone1_out;
tone1_counter <= tone1_div_val;
end if;
if unsigned(tone2_counter) = 0 then
tone2_out <= not tone2_out;
tone2_counter <= tone2_div_val;
end if;
bump_noise <= '0';
if unsigned(tone3_counter) = 0 then
tone3_out <= not tone3_out;
tone3_counter <= tone3_div_val;
if noise_div_val(1 downto 0) = "11" then
bump_noise <= '1';
end if;
end if;
if noise_counter(8 downto 0) = "000000000" then
case noise_div_val(1 downto 0) is
when "00" => bump_noise <= '1'; -- 512
when "01" =>
if noise_counter(9)='0' then -- 1024
bump_noise <= '1';
end if;
when "10" =>
if noise_counter(10 downto 9)="00" then -- 2048
bump_noise <= '1';
end if;
when others =>
end case;
end if;
when others =>
end case;
end if;
if bump_noise='1' then
if noise_div_val(2)='1' then
-- white noise
k := noise_lfsr(14) xor noise_lfsr(13);
else
k := noise_lfsr(14); -- just feedback
end if;
noise_lfsr <= noise_lfsr(14 downto 0) & k;
if noise_lfsr(14) = '1' then
noise_out <= not noise_out;
end if;
end if;
end if;
if add_flag='1' then
acc <= std_logic_vector(to_unsigned(
to_integer(unsigned(acc)) + to_integer(unsigned(volume_lookup(add_value))),
acc'length));
else
acc <= std_logic_vector(to_unsigned(
to_integer(unsigned(acc)) - to_integer(unsigned(volume_lookup(add_value))),
acc'length));
end if;
-- Ok now combine the tone_out values
case tone_proc is
when chan0 =>
add_value <= tone1_att;
add_flag <= tone1_out;
tone_proc <= chan1;
when chan1 =>
add_value <= tone2_att;
add_flag <= tone2_out;
tone_proc <= chan2;
when chan2 =>
add_value <= tone3_att;
add_flag <= tone3_out;
tone_proc <= noise;
when noise =>
add_value <= noise_att;
add_flag <= noise_out;
tone_proc <= prepare;
when prepare =>
-- During this step the acc gets updated with noise value
add_value <= "1111"; -- silence, this stage is just a wait state to pick up noise
tone_proc <= output;
when others => -- output stage
dac_out <= acc;
add_value <= "1111"; -- no change
acc <= x"80";
tone_proc <= chan0;
end case;
end if;
end process;
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.