Close

Sound!

A project log for RC2016/99: TI-99/4A clone using TMS99105 CPU

Retrochallenge 2016/10 entry and winner: try to build a TI-99/4A clone using a TMS99105 CPU and an FPGA. In a month...

erik-piehlErik Piehl 10/23/2016 at 20:570 Comments

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