Last modified on 11 March 2018, at 05:51

Ym2149

Yamaha Ym2149 sound chip created by MikeJ in VHDL, compatible with Xilinx and Altera.

MikeJ started by implementing the 555 chip in VHDL, used for sound effect (about five different sound) in original Space Invaders arcade game... and then realised this VHDL version of Yamaha Ym2149 sound chip compatible with Amstrad CPC.

Not published, YM2149_linmix.vhd original version (coming from FPGAmstrad NEXYS4 v0 backup), patched to run in FPGAmstrad (see "freemac" occurrence in code) :

--
-- A simulation model of YM2149 (AY-3-8910 with bells on)

-- Copyright (c) MikeJ - Jan 2005
--
-- All rights reserved
--
-- Redistribution and use in source and synthezised forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- Redistributions in synthesized form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- Neither the name of the author nor the names of other contributors may
-- be used to endorse or promote products derived from this software without
-- specific prior written permission.
--
-- THIS CODE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
-- THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-- PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE
-- LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-- CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--
-- You are responsible for any legal issues arising from your use of this code.
--
-- The latest version of this file can be found at: www.fpgaarcade.com
--
-- Email support@fpgaarcade.com
--
-- Revision list
--
-- version 001 initial release
--
-- Clues from MAME sound driver and Kazuhiro TSUJIKAWA
--
-- These are the measured outputs from a real chip for a single Isolated channel into a 1K load (V)
-- vol 15 .. 0
-- 3.27 2.995 2.741 2.588 2.452 2.372 2.301 2.258 2.220 2.198 2.178 2.166 2.155 2.148 2.141 2.132
-- As the envelope volume is 5 bit, I have fitted a curve to the not quite log shape in order
-- to produced all the required values.
-- (The first part of the curve is a bit steeper and the last bit is more linear than expected)
--
-- NOTE, this component uses LINEAR mixing of the three analogue channels, and is only
-- accurate for designs where the outputs are buffered and not simply wired together.
-- The ouput level is more complex in that case and requires a larger table.

library ieee;
  use ieee.std_logic_1164.all;
  use ieee.std_logic_arith.all;
  use ieee.std_logic_unsigned.all;

entity YM2149 is
	generic (
		MOCK:boolean:=false
	);
  port (
  -- data bus
  I_DA                : in  std_logic_vector(7 downto 0);
  O_DA                : out std_logic_vector(7 downto 0);
  O_DA_OE_L           : out std_logic;
  -- control
  I_A9_L              : in  std_logic;
  I_A8                : in  std_logic;
  I_BDIR              : in  std_logic;
  I_BC2               : in  std_logic;
  I_BC1               : in  std_logic;
  I_SEL_L             : in  std_logic;

  O_AUDIO             : out std_logic_vector(7 downto 0);
  -- port a
  I_IOA               : in  std_logic_vector(7 downto 0);
--  O_IOA               : out std_logic_vector(7 downto 0);
--  O_IOA_OE_L          : out std_logic;
  -- port b
--  I_IOB               : in  std_logic_vector(7 downto 0);
--  O_IOB               : out std_logic_vector(7 downto 0);
--  O_IOB_OE_L          : out std_logic;

  ENA                 : in  std_logic; -- clock enable for higher speed operation
  RESET_L             : in  std_logic;
  CLK                 : in  std_logic  -- note 6 Mhz
  );
end;

architecture RTL of YM2149 is
  --type  array_16x8   is array (0 to 15) of std_logic_vector(7 downto 0);
  --type  array_16x8   is array (0 to 14) of std_logic_vector(7 downto 0);
  type  array_16x8   is array (0 to 13) of std_logic_vector(7 downto 0);
  type  array_3x12   is array (1 to 3) of std_logic_vector(11 downto 0);

  signal cnt_div              : std_logic_vector(3 downto 0) := (others => '0');
  signal noise_div            : std_logic := '0';
  signal ena_div              : std_logic;
  signal ena_div_noise        : std_logic;
  signal poly17               : std_logic_vector(16 downto 0) := (others => '0');

  -- registers
  signal addr                 : std_logic_vector(7 downto 0);
  signal busctrl_addr         : std_logic;
  signal busctrl_we           : std_logic;
  signal busctrl_re           : std_logic;

  signal reg                  : array_16x8;
  signal env_reset            : std_logic;
  signal ioa_inreg            : std_logic_vector(7 downto 0);
  --signal iob_inreg            : std_logic_vector(7 downto 0);

  signal noise_gen_cnt        : std_logic_vector(4 downto 0);
  signal noise_gen_op         : std_logic;
  signal tone_gen_cnt         : array_3x12 := (others => (others => '0'));
  signal tone_gen_op          : std_logic_vector(3 downto 1) := "000";

  signal env_gen_cnt          : std_logic_vector(15 downto 0);
  signal env_ena              : std_logic;
  signal env_hold             : std_logic;
  signal env_inc              : std_logic;
  signal env_vol              : std_logic_vector(4 downto 0);

  signal tone_ena_l           : std_logic;
  signal tone_src             : std_logic;
  signal noise_ena_l          : std_logic;
  signal chan_vol             : std_logic_vector(4 downto 0);

  signal dac_amp              : std_logic_vector(7 downto 0);
  signal audio_mix            : std_logic_vector(9 downto 0);
  signal audio_final          : std_logic_vector(9 downto 0);
begin

do_mock:if MOCK generate
	O_DA<=I_IOA;
end generate;

dont_mock:if not(MOCK) generate

  -- cpu i/f
  p_busdecode            : process(I_BDIR, I_BC2, I_BC1, addr, I_A9_L, I_A8)
    variable cs : std_logic;
    variable sel : std_logic_vector(2 downto 0);
  begin
    -- BDIR BC2 BC1 MODE
    --   0   0   0  inactive
    --   0   0   1  address
    --   0   1   0  inactive
    --   0   1   1  read
    --   1   0   0  address
    --   1   0   1  inactive
    --   1   1   0  write
    --   1   1   1  read
    busctrl_addr <= '0';
    busctrl_we <= '0';
    busctrl_re <= '0';

    cs := '0';
    if (I_A9_L = '0') and (I_A8 = '1') and (addr(7 downto 4) = "0000") then
      cs := '1';
    end if;

    sel := (I_BDIR & I_BC2 & I_BC1);
    case sel is
      when "000" => null;
      when "001" => busctrl_addr <= '1';
      when "010" => null;
      when "011" => busctrl_re   <= cs;
      when "100" => busctrl_addr <= '1';
      when "101" => null;
      when "110" => busctrl_we   <= cs;
      when "111" => busctrl_addr <= '1';
      when others => null;
    end case;
  end process;

  p_oe                   : process(busctrl_re)
  begin
    -- if we are emulating a real chip, maybe clock this to fake up the tristate typ delay of 100ns
    O_DA_OE_L <= not (busctrl_re);
  end process;

  --
  -- CLOCKED
  --
  --p_waddr                : process
  --begin
    ---- looks like registers are latches in real chip, but the address is caught at the end of the address state.
    --wait until rising_edge(CLK);

    --if (RESET_L = '0') then
      --addr <= (others => '0');
    --else
      --if (busctrl_addr = '1') then
        --addr <= I_DA;
      --end if;
    --end if;
  --end process;

  --p_wdata                : process
  --begin
    ---- looks like registers are latches in real chip, but the address is caught at the end of the address state.
    --wait until rising_edge(CLK);
    --env_reset <= '0';

    --if (RESET_L = '0') then
      --reg <= (others => (others => '0'));
      --env_reset <= '1';
    --else
      --env_reset <= '0';
      --if (busctrl_we = '1') then
        --case addr(3 downto 0) is
          --when x"0" => reg(0)  <= I_DA;
          --when x"1" => reg(1)  <= I_DA;
          --when x"2" => reg(2)  <= I_DA;
          --when x"3" => reg(3)  <= I_DA;
          --when x"4" => reg(4)  <= I_DA;
          --when x"5" => reg(5)  <= I_DA;
          --when x"6" => reg(6)  <= I_DA;
          --when x"7" => reg(7)  <= I_DA;
          --when x"8" => reg(8)  <= I_DA;
          --when x"9" => reg(9)  <= I_DA;
          --when x"A" => reg(10) <= I_DA;
          --when x"B" => reg(11) <= I_DA;
          --when x"C" => reg(12) <= I_DA;
          --when x"D" => reg(13) <= I_DA; env_reset <= '1';
          --when x"E" => reg(14) <= I_DA;
          --when x"F" => reg(15) <= I_DA;
          --when others => null;
        --end case;
      --end if;
    --end if;
  --end process;

  --
  -- LATCHED, useful when emulating a real chip in circuit. Nasty as gated clock.
  --
  p_waddr                : process(reset_l, busctrl_addr)
  begin
    -- looks like registers are latches in real chip, but the address is caught at the end of the address state.
    if (RESET_L = '0') then
      addr <= (others => '0');
    elsif falling_edge(busctrl_addr) then -- yuk
      addr <= I_DA;
    end if;
  end process;

  p_wdata                : process(reset_l, clk, addr) --process(reset_l, busctrl_we, addr)
  begin
    if (RESET_L = '0') then
      reg <= (others => (others => '0'));
    elsif rising_edge(clk) then
		if busctrl_we='1' then --falling_edge(busctrl_we) then
        case addr(3 downto 0) is
          when x"0" => reg(0)  <= I_DA;
          when x"1" => reg(1)  <= I_DA;
          when x"2" => reg(2)  <= I_DA;
          when x"3" => reg(3)  <= I_DA;
          when x"4" => reg(4)  <= I_DA;
          when x"5" => reg(5)  <= I_DA;
          when x"6" => reg(6)  <= I_DA;
          when x"7" => reg(7)  <= I_DA;
          when x"8" => reg(8)  <= I_DA;
          when x"9" => reg(9)  <= I_DA;
          when x"A" => reg(10) <= I_DA;
          when x"B" => reg(11) <= I_DA;
          when x"C" => reg(12) <= I_DA;
          when x"D" => reg(13) <= I_DA;
 --         when x"E" => reg(14) <= I_DA;
--          when x"F" => reg(15) <= I_DA;
          when others => null;
        end case;
		  end if;
    end if;

    env_reset <= '0';
    if (busctrl_we = '1') and (addr(3 downto 0) = x"D") then
      env_reset <= '1';
    end if;
  end process;

  p_rdata                : process(busctrl_re, addr, reg)
  begin
    O_DA <= (others => '0'); -- 'X'
    if (busctrl_re = '1') then -- not necessary, but useful for putting 'X's in the simulator
      case addr(3 downto 0) is
        when x"0" => O_DA <= reg(0) ;
        when x"1" => O_DA <= "0000" & reg(1)(3 downto 0) ;
        when x"2" => O_DA <= reg(2) ;
        when x"3" => O_DA <= "0000" & reg(3)(3 downto 0) ;
        when x"4" => O_DA <= reg(4) ;
        when x"5" => O_DA <= "0000" & reg(5)(3 downto 0) ;
        when x"6" => O_DA <= "000"  & reg(6)(4 downto 0) ;
        when x"7" => O_DA <= reg(7) ;
        when x"8" => O_DA <= "000"  & reg(8)(4 downto 0) ;
        when x"9" => O_DA <= "000"  & reg(9)(4 downto 0) ;
        when x"A" => O_DA <= "000"  & reg(10)(4 downto 0) ;
        when x"B" => O_DA <= reg(11);
        when x"C" => O_DA <= reg(12);
        when x"D" => O_DA <= "0000" & reg(13)(3 downto 0);
        when x"E" => if (reg(7)(6) = '0') then -- input
                       O_DA <= ioa_inreg;
                     else
                       O_DA <= ioa_inreg; -- freemac hack reg(14); -- read output reg
                     end if;
--        when x"F" => if (Reg(7)(7) = '0') then
--                       O_DA <= iob_inreg;
--                     else
--                       O_DA <= reg(15);
--                     end if;
        when others => null;
      end case;
    end if;
  end process;
  --
  p_divider              : process
  begin
    wait until rising_edge(CLK);
    -- / 8 when SEL is high and /16 when SEL is low
    if (ENA = '1') then
      ena_div <= '0';
      ena_div_noise <= '0';
      if (cnt_div = "0000") then
        cnt_div <= (not I_SEL_L) & "111";
        ena_div <= '1';

        noise_div <= not noise_div;
        if (noise_div = '1') then
          ena_div_noise <= '1';
        end if;
      else
        cnt_div <= cnt_div - "1";
      end if;
    end if;
  end process;

  p_noise_gen            : process
    variable noise_gen_comp : std_logic_vector(4 downto 0);
    variable poly17_zero : std_logic;
  begin
    wait until rising_edge(CLK);

    if (reg(6)(4 downto 0) = "00000") then
      noise_gen_comp := "00000";
    else
      noise_gen_comp := (reg(6)(4 downto 0) - "1");
    end if;

    poly17_zero := '0';
    if (poly17 = "00000000000000000") then poly17_zero := '1'; end if;

    if (ENA = '1') then

      if (ena_div_noise = '1') then -- divider ena

        if (noise_gen_cnt >= noise_gen_comp) then
          noise_gen_cnt <= "00000";
          poly17 <= (poly17(0) xor poly17(2) xor poly17_zero) & poly17(16 downto 1);
        else
          noise_gen_cnt <= (noise_gen_cnt + "1");
        end if;
      end if;
    end if;
  end process;
  noise_gen_op <= poly17(0);

  p_tone_gens            : process
    variable tone_gen_freq : array_3x12;
    variable tone_gen_comp : array_3x12;
  begin
    wait until rising_edge(CLK);

    -- looks like real chips count up - we need to get the Exact behaviour ..
    tone_gen_freq(1) := reg(1)(3 downto 0) & reg(0);
    tone_gen_freq(2) := reg(3)(3 downto 0) & reg(2);
    tone_gen_freq(3) := reg(5)(3 downto 0) & reg(4);
    -- period 0 = period 1
    for i in 1 to 3 loop
      if (tone_gen_freq(i) = x"000") then
        tone_gen_comp(i) := x"000";
      else
        tone_gen_comp(i) := (tone_gen_freq(i) - "1");
      end if;
    end loop;

    if (ENA = '1') then
      for i in 1 to 3 loop
        if (ena_div = '1') then -- divider ena

          if (tone_gen_cnt(i) >= tone_gen_comp(i)) then
            tone_gen_cnt(i) <= x"000";
            tone_gen_op(i) <= not tone_gen_op(i);
          else
            tone_gen_cnt(i) <= (tone_gen_cnt(i) + "1");
          end if;
        end if;
      end loop;
    end if;
  end process;

  p_envelope_freq        : process
    variable env_gen_freq : std_logic_vector(15 downto 0);
    variable env_gen_comp : std_logic_vector(15 downto 0);
  begin
    wait until rising_edge(CLK);
    env_gen_freq := reg(12) & reg(11);
    -- envelope freqs 1 and 0 are the same.
    if (env_gen_freq = x"0000") then
      env_gen_comp := x"0000";
    else
      env_gen_comp := (env_gen_freq - "1");
    end if;

    if (ENA = '1') then
      env_ena <= '0';
      if (ena_div = '1') then -- divider ena
        if (env_gen_cnt >= env_gen_comp) then
          env_gen_cnt <= x"0000";
          env_ena <= '1';
        else
          env_gen_cnt <= (env_gen_cnt + "1");
        end if;
      end if;
    end if;
  end process;

  p_envelope_shape       : process(env_reset, CLK)
    variable is_bot    : boolean;
    variable is_bot_p1 : boolean;
    variable is_top_m1 : boolean;
    variable is_top    : boolean;
  begin
        -- envelope shapes
        -- C AtAlH
        -- 0 0 x x  \___
        --
        -- 0 1 x x  /___
        --
        -- 1 0 0 0  \\\\
        --
        -- 1 0 0 1  \___
        --
        -- 1 0 1 0  \/\/
        --           ___
        -- 1 0 1 1  \
        --
        -- 1 1 0 0  ////
        --           ___
        -- 1 1 0 1  /
        --
        -- 1 1 1 0  /\/\
        --
        -- 1 1 1 1  /___
    if (env_reset = '1') then
      -- load initial state
      if (reg(13)(2) = '0') then -- attack
        env_vol <= "11111";
        env_inc <= '0'; -- -1
      else
        env_vol <= "00000";
        env_inc <= '1'; -- +1
      end if;
      env_hold <= '0';

    elsif rising_edge(CLK) then
      is_bot    := (env_vol = "00000");
      is_bot_p1 := (env_vol = "00001");
      is_top_m1 := (env_vol = "11110");
      is_top    := (env_vol = "11111");

      if (ENA = '1') then
        if (env_ena = '1') then
          if (env_hold = '0') then
            if (env_inc = '1') then
              env_vol <= (env_vol + "00001");
            else
              env_vol <= (env_vol + "11111");
            end if;
          end if;

          -- envelope shape control.
          if (reg(13)(3) = '0') then
            if (env_inc = '0') then -- down
              if is_bot_p1 then env_hold <= '1'; end if;
            else
              if is_top then env_hold <= '1'; end if;
            end if;
          else
            if (reg(13)(0) = '1') then -- hold = 1
              if (env_inc = '0') then -- down
                if (reg(13)(1) = '1') then -- alt
                  if is_bot    then env_hold <= '1'; end if;
                else
                  if is_bot_p1 then env_hold <= '1'; end if;
                end if;
              else
                if (reg(13)(1) = '1') then -- alt
                  if is_top    then env_hold <= '1'; end if;
                else
                  if is_top_m1 then env_hold <= '1'; end if;
                end if;
              end if;

            elsif (reg(13)(1) = '1') then -- alternate
              if (env_inc = '0') then -- down
                if is_bot_p1 then env_hold <= '1'; end if;
                if is_bot    then env_hold <= '0'; env_inc <= '1'; end if;
              else
                if is_top_m1 then env_hold <= '1'; end if;
                if is_top    then env_hold <= '0'; env_inc <= '0'; end if;
              end if;
            end if;

          end if;
        end if;
      end if;
    end if;
  end process;

  p_chan_mixer           : process(cnt_div, reg, tone_gen_op)
  begin
    tone_ena_l  <= '1'; tone_src <= '1';
    noise_ena_l <= '1'; chan_vol <= "00000";
    case cnt_div(1 downto 0) is
      when "00" =>
        tone_ena_l  <= reg(7)(0); tone_src <= tone_gen_op(1); chan_vol <=  reg(8)(4 downto 0);
        noise_ena_l <= reg(7)(3);
      when "01" =>
        tone_ena_l  <= reg(7)(1); tone_src <= tone_gen_op(2); chan_vol <=  reg(9)(4 downto 0);
        noise_ena_l <= reg(7)(4);
      when "10" =>
        tone_ena_l  <= reg(7)(2); tone_src <= tone_gen_op(3); chan_vol <= reg(10)(4 downto 0);
        noise_ena_l <= reg(7)(5);
      when "11" => null; -- tone gen outputs become valid on this clock
      when others => null;
    end case;
  end process;

  p_op_mixer             : process
    variable chan_mixed : std_logic;
    variable chan_amp : std_logic_vector(4 downto 0);
  begin
    wait until rising_edge(CLK);
    if (ENA = '1') then

      chan_mixed := (tone_ena_l or tone_src) and (noise_ena_l or noise_gen_op);

      chan_amp := (others => '0');
      if (chan_mixed = '1') then
        if (chan_vol(4) = '0') then
          if (chan_vol(3 downto 0) = "0000") then -- nothing is easy ! make sure quiet is quiet
            chan_amp := "00000";
          else
            chan_amp := chan_vol(3 downto 0) & '1'; -- make sure level 31 (env) = level 15 (tone)
          end if;
        else
          chan_amp := env_vol(4 downto 0);
        end if;
      end if;

      dac_amp <= x"00";
      case chan_amp is
        when "11111" => dac_amp <= x"FF";
        when "11110" => dac_amp <= x"D9";
        when "11101" => dac_amp <= x"BA";
        when "11100" => dac_amp <= x"9F";
        when "11011" => dac_amp <= x"88";
        when "11010" => dac_amp <= x"74";
        when "11001" => dac_amp <= x"63";
        when "11000" => dac_amp <= x"54";
        when "10111" => dac_amp <= x"48";
        when "10110" => dac_amp <= x"3D";
        when "10101" => dac_amp <= x"34";
        when "10100" => dac_amp <= x"2C";
        when "10011" => dac_amp <= x"25";
        when "10010" => dac_amp <= x"1F";
        when "10001" => dac_amp <= x"1A";
        when "10000" => dac_amp <= x"16";
        when "01111" => dac_amp <= x"13";
        when "01110" => dac_amp <= x"10";
        when "01101" => dac_amp <= x"0D";
        when "01100" => dac_amp <= x"0B";
        when "01011" => dac_amp <= x"09";
        when "01010" => dac_amp <= x"08";
        when "01001" => dac_amp <= x"07";
        when "01000" => dac_amp <= x"06";
        when "00111" => dac_amp <= x"05";
        when "00110" => dac_amp <= x"04";
        when "00101" => dac_amp <= x"03";
        when "00100" => dac_amp <= x"03";
        when "00011" => dac_amp <= x"02";
        when "00010" => dac_amp <= x"02";
        when "00001" => dac_amp <= x"01";
        when "00000" => dac_amp <= x"00";
        when others => null;
      end case;

      if (cnt_div(1 downto 0) = "10") then
        audio_mix   <= (others => '0');
        audio_final <= audio_mix;
      else
        audio_mix   <= audio_mix + ("00" & dac_amp);
      end if;

      if (RESET_L = '0') then
        O_AUDIO(7 downto 0) <= "00000000";
      else
        if (audio_final(9) = '0') then
          O_AUDIO(7 downto 0) <= audio_final(8 downto 1);
        else -- clip
          O_AUDIO(7 downto 0) <= x"FF";
        end if;
      end if;
    end if;
  end process;

--  p_io_ports             : process(reg)
--  begin
--    --O_IOA <= reg(14);
--
--    --O_IOA_OE_L <= not reg(7)(6);
----    O_IOB <= reg(15);
----    O_IOB_OE_L <= not reg(7)(7);
--  end process;

  p_io_ports_inreg       : process
  begin
    wait until rising_edge(CLK);
    ioa_inreg <= I_IOA;
--    iob_inreg <= I_IOB;
  end process;
  
end generate;
end architecture RTL;

This chip is used in FPGAmstrad project in order to implement Amstrad core in MiST-board (Altera final platform)

MikeJ is also author of :