Ym2149 chip created by MikeJ in VHDL, compatible with Xilinx and Altera.
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;
MikeJ is also author of :
- FPGAArcade platform (Xilinx final platform running AGA Amiga)
- T80 chip (VHDL) used also in FPGAmstrad project