mirror of
https://github.com/Gehstock/Mist_FPGA.git
synced 2026-03-01 09:21:14 +00:00
909 lines
31 KiB
VHDL
909 lines
31 KiB
VHDL
--
|
|
-- SN76489 Complex Sound Generator
|
|
-- Matthew Hagerty
|
|
-- July 2020
|
|
-- https://dnotq.io
|
|
--
|
|
|
|
-- Released under the 3-Clause BSD License:
|
|
--
|
|
-- Copyright 2020 Matthew Hagerty (matthew <at> dnotq <dot> io)
|
|
--
|
|
-- Redistribution and use in source and binary forms, with or without
|
|
-- modification, are permitted provided that the following conditions are met:
|
|
--
|
|
-- 1. Redistributions of source code must retain the above copyright notice,
|
|
-- this list of conditions and the following disclaimer.
|
|
--
|
|
-- 2. Redistributions in binary 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.
|
|
--
|
|
-- 3. Neither the name of the copyright holder nor the names of its
|
|
-- contributors may be used to endorse or promote products derived from this
|
|
-- software without specific prior written permission.
|
|
--
|
|
-- THIS SOFTWARE 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 COPYRIGHT HOLDER 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.
|
|
|
|
--
|
|
-- A huge amount of effort has gone into making this core as accurate as
|
|
-- possible to the real IC, while at the same time making it usable in all
|
|
-- digital SoC designs, i.e. retro-computer and game systems, etc. Design
|
|
-- elements from the real IC were used and implemented when possible, with any
|
|
-- work-around or changes noted along with the reasons.
|
|
--
|
|
-- Synthesized and FPGA proven:
|
|
--
|
|
-- * Xilinx Spartan-6 LX16, SoC 21.477MHz system clock, 3.58MHz clock-enable.
|
|
--
|
|
--
|
|
-- References:
|
|
--
|
|
-- * The SN76489 datasheet
|
|
-- * Insight gained from the AY-3-8910/YM-2149 die-shot and reverse-engineered
|
|
-- schematics (similar audio chips from the same era).
|
|
-- * Real hardware (SN76489 in a ColecoVision game console).
|
|
-- * Chip quirks, use, and abuse details from friends and retro enthusiasts.
|
|
--
|
|
--
|
|
-- Generates:
|
|
--
|
|
-- * Unsigned 12-bit output for each channel.
|
|
-- * Unsigned 14-bit summation of the four channels.
|
|
-- * Signed 14-bit PCM summation of the four channels, with each channel
|
|
-- converted to -/+ zero-centered level or -/+ full-range level.
|
|
--
|
|
-- The tone counters are period-limited to prevent the very high frequency
|
|
-- outputs that the original IC is capable of producing. Frequencies above
|
|
-- 20KHz cause problems in all-digital systems with sampling rates around
|
|
-- 44.1KHz to 48KHz. The primary use of these high frequencies was as a
|
|
-- carrier for amplitude modulated (AM) audio. The high frequency would be
|
|
-- filtered out by external electronics, leaving only the low frequency audio.
|
|
--
|
|
-- When the tone counters are limited, the output square-wave is disabled, but
|
|
-- the amplitude can still be changed, which allows the A.M. technique to still
|
|
-- work in a digital Soc.
|
|
--
|
|
-- I/O requires at least two clock-enable cycles. This could be modified to
|
|
-- operate faster, i.e. based on the input-clock directly. All inputs are
|
|
-- registered at the system-clock rate.
|
|
--
|
|
-- Optionally simulates the original 32-clock (clock-enable) I/O cycle.
|
|
--
|
|
-- The SN76489 does not have an external reset and the original IC "wakes up"
|
|
-- generating a tone. This implementation sets the default output level to
|
|
-- full attenuation (silent output). If the original functionality is desired,
|
|
-- modify the channel period and level register initial values.
|
|
|
|
--
|
|
-- Basic I/O interface use:
|
|
--
|
|
-- Set-up data on data_i.
|
|
-- Set ce_n_i and wr_n_i low.
|
|
-- Observe ready_o and wait for it to become high.
|
|
-- Set wr_n_i high, if done writing to the chip set ce_n_i high.
|
|
|
|
--
|
|
-- Version history:
|
|
--
|
|
-- July 21 2020
|
|
-- V1.0. Release. SoC tested.
|
|
--
|
|
|
|
|
|
library ieee;
|
|
use ieee.std_logic_1164.all;
|
|
use ieee.numeric_std.all;
|
|
|
|
entity sn76489_audio is
|
|
generic -- 0 = normal I/O, 32 clocks per write
|
|
-- 1 = fast I/O, around 2 clocks per write
|
|
( FAST_IO_G : std_logic := '0'
|
|
|
|
-- Minimum allowable period count (see comments further
|
|
-- down for more information), recommended:
|
|
-- 6 18643.46Hz First audible count.
|
|
-- 17 6580.04Hz Counts at 16 are known to be used for
|
|
-- amplitude-modulation.
|
|
; MIN_PERIOD_CNT_G : integer := 6
|
|
);
|
|
port
|
|
( clk_i : in std_logic -- System clock
|
|
; en_clk_psg_i : in std_logic -- PSG clock enable
|
|
; ce_n_i : in std_logic -- chip enable, active low
|
|
; wr_n_i : in std_logic -- write enable, active low
|
|
; ready_o : out std_logic -- low during I/O operations
|
|
; data_i : in std_logic_vector(7 downto 0)
|
|
; ch_a_o : out unsigned(11 downto 0)
|
|
; ch_b_o : out unsigned(11 downto 0)
|
|
; ch_c_o : out unsigned(11 downto 0)
|
|
; noise_o : out unsigned(11 downto 0)
|
|
; mix_audio_o : out unsigned(13 downto 0)
|
|
; pcm14s_o : out unsigned(13 downto 0)
|
|
);
|
|
end sn76489_audio;
|
|
|
|
architecture rtl of sn76489_audio is
|
|
|
|
-- Registered I/O.
|
|
signal ce_n_r : std_logic := '1';
|
|
signal wr_n_r : std_logic := '1';
|
|
signal din_r : unsigned(7 downto 0) := x"00";
|
|
signal ready_r : std_logic := '1';
|
|
signal ready_x : std_logic;
|
|
|
|
-- I/O FSM.
|
|
type io_state_t is (IO_IDLE, IO_OP, IO_WAIT);
|
|
signal io_state_r : io_state_t := IO_IDLE;
|
|
signal io_state_x : io_state_t;
|
|
signal io_cnt_r : unsigned(4 downto 0) := (others => '0');
|
|
signal io_cnt_x : unsigned(4 downto 0);
|
|
signal en_reg_wr_s : std_logic;
|
|
|
|
-- Register file.
|
|
signal reg_addr_r : unsigned(2 downto 0) := (others => '0');
|
|
signal reg_sel_s : unsigned(2 downto 0);
|
|
|
|
signal ch_a_period_r : unsigned(9 downto 0) := (others => '0');
|
|
signal ch_a_period_x : unsigned(9 downto 0);
|
|
signal ch_a_level_r : unsigned(11 downto 0) := (others => '0');
|
|
signal ch_a_level_x : unsigned(11 downto 0);
|
|
|
|
signal ch_b_period_r : unsigned(9 downto 0) := (others => '0');
|
|
signal ch_b_period_x : unsigned(9 downto 0);
|
|
signal ch_b_level_r : unsigned(11 downto 0) := (others => '0');
|
|
signal ch_b_level_x : unsigned(11 downto 0);
|
|
|
|
signal ch_c_period_r : unsigned(9 downto 0) := (others => '0');
|
|
signal ch_c_period_x : unsigned(9 downto 0);
|
|
signal ch_c_level_r : unsigned(11 downto 0) := (others => '0');
|
|
signal ch_c_level_x : unsigned(11 downto 0);
|
|
|
|
signal noise_ctrl_r : std_logic := '0';
|
|
signal noise_ctrl_x : std_logic;
|
|
signal noise_shift_r : unsigned(1 downto 0) := (others => '0');
|
|
signal noise_shift_x : unsigned(1 downto 0);
|
|
signal noise_level_r : unsigned(11 downto 0) := (others => '0');
|
|
signal noise_level_x : unsigned(11 downto 0);
|
|
signal noise_rst_r : std_logic := '0';
|
|
signal noise_rst_x : std_logic;
|
|
|
|
-- Clock conditioning counters and enables.
|
|
signal clk_div16_r : unsigned(3 downto 0) := (others => '0');
|
|
signal clk_div16_x : unsigned(3 downto 0);
|
|
signal en_cnt_r : std_logic := '0';
|
|
signal en_cnt_x : std_logic;
|
|
|
|
-- Channel tone counters.
|
|
signal ch_a_cnt_r : unsigned(9 downto 0) := (others => '0');
|
|
signal ch_a_cnt_x : unsigned(9 downto 0);
|
|
signal flatline_a_s : std_logic;
|
|
signal tone_a_r : std_logic := '1';
|
|
signal tone_a_x : std_logic;
|
|
|
|
signal ch_b_cnt_r : unsigned(9 downto 0) := (others => '0');
|
|
signal ch_b_cnt_x : unsigned(9 downto 0);
|
|
signal flatline_b_s : std_logic;
|
|
signal tone_b_r : std_logic := '1';
|
|
signal tone_b_x : std_logic;
|
|
|
|
signal ch_c_cnt_r : unsigned(9 downto 0) := (others => '0');
|
|
signal ch_c_cnt_x : unsigned(9 downto 0);
|
|
signal flatline_c_s : std_logic;
|
|
signal tone_c_r : std_logic := '1';
|
|
signal tone_c_x : std_logic;
|
|
signal c_ff_r : std_logic := '1';
|
|
signal c_ff_x : std_logic;
|
|
|
|
-- Noise counter.
|
|
signal noise_cnt_r : unsigned(6 downto 0) := (others => '0');
|
|
signal noise_cnt_x : unsigned(6 downto 0);
|
|
signal noise_ff_r : std_logic := '1';
|
|
signal noise_ff_x : std_logic;
|
|
|
|
-- 15-bit Noise LFSR.
|
|
signal noise_lfsr_r : std_logic_vector(14 downto 0) := b"100_0000_0000_0000";
|
|
signal noise_lfsr_x : std_logic_vector(14 downto 0);
|
|
signal noise_fb_s : std_logic;
|
|
signal noise_s : std_logic;
|
|
|
|
-- Amplitude / Attenuation control.
|
|
signal level_a_s : unsigned(11 downto 0);
|
|
signal level_b_s : unsigned(11 downto 0);
|
|
signal level_c_s : unsigned(11 downto 0);
|
|
signal level_n_s : unsigned(11 downto 0);
|
|
|
|
-- DAC.
|
|
signal dac_a_r : unsigned(11 downto 0) := (others => '0');
|
|
signal dac_b_r : unsigned(11 downto 0) := (others => '0');
|
|
signal dac_c_r : unsigned(11 downto 0) := (others => '0');
|
|
signal dac_n_r : unsigned(11 downto 0) := (others => '0');
|
|
signal sum_audio_r : unsigned(13 downto 0) := (others => '0');
|
|
|
|
-- Digital to Analogue Output-level lookup table ROM.
|
|
--
|
|
-- The 4-bit level from the channel register is used as an index into a
|
|
-- calculated table of values that represent the equivalent voltage.
|
|
--
|
|
-- The output scale is amplitude logarithmic.
|
|
--
|
|
-- ln10 = Natural logarithm of 10, ~= 2.302585
|
|
-- amp = Amplitude in voltage, 0.2, 1.45, etc.
|
|
-- dB = decibel value in dB, -1.5, -3, etc.
|
|
--
|
|
-- dB = 20 * log(amp) / ln10
|
|
-- amp = 10 ^ (dB / 20)
|
|
--
|
|
-- -1.5dB = 0.8413951416
|
|
-- -2.0dB = 0.7943282347
|
|
-- -3.0dB = 0.7079457843
|
|
--
|
|
-- The datasheet defines 16 attenuation steps that are -2.0dB apart.
|
|
--
|
|
-- 1V reference values based on sixteen 2.0dB steps:
|
|
--
|
|
-- 1.0000, 0.7943, 0.6310, 0.5012, 0.3981, 0.3162, 0.2512, 0.1995,
|
|
-- 0.1585, 0.1259, 0.1000, 0.0794, 0.0631, 0.0501, 0.0398, 0.0316
|
|
--
|
|
-- A 7-bit number (0..128) can support a scaled version of the reference
|
|
-- list without having any duplicate values, but the difference is small at
|
|
-- the bottom-end. Duplicate values means several volume levels produce the
|
|
-- same output level, and is not accurate.
|
|
--
|
|
-- Using using a 12-bit output value means the four channels can be summed
|
|
-- into a 14-bit value without overflow, and leaves room for adjustments if
|
|
-- converting to something like 16-bit PCM. The 12-bit values also provide
|
|
-- a nicer curve, and are easier to initialize in VHDL.
|
|
--
|
|
-- The lowest volume level needs to go to 0 in a digital SoC to prevent
|
|
-- noise that would be filtered in a real system with external electronics.
|
|
|
|
signal dac_level_s : unsigned(11 downto 0);
|
|
|
|
type dacrom_type is array (0 to 15) of unsigned(11 downto 0);
|
|
signal dacrom_ar : dacrom_type := (
|
|
-- 4095,3253,2584,2052,1630,1295,1029, 817,
|
|
-- 649, 516, 409, 325, 258, 205, 163, 129 (forced to 0)
|
|
x"FFF",x"CB5",x"A18",x"804",x"65E",x"50F",x"405",x"331",
|
|
x"289",x"204",x"199",x"145",x"102",x"0CD",x"0A3",x"000");
|
|
|
|
-- PCM signed 14-bit.
|
|
signal sign_a_r : unsigned(11 downto 0) := (others => '0');
|
|
signal sign_a_x : unsigned(11 downto 0);
|
|
signal sign_b_r : unsigned(11 downto 0) := (others => '0');
|
|
signal sign_b_x : unsigned(11 downto 0);
|
|
signal sign_c_r : unsigned(11 downto 0) := (others => '0');
|
|
signal sign_c_x : unsigned(11 downto 0);
|
|
signal sign_n_r : unsigned(11 downto 0) := (others => '0');
|
|
signal sign_n_x : unsigned(11 downto 0);
|
|
signal pcm14s_r : unsigned(13 downto 0) := (others => '0');
|
|
|
|
begin
|
|
|
|
-- Register the input data at the full clock rate.
|
|
process ( clk_i ) begin
|
|
if rising_edge(clk_i) then
|
|
ce_n_r <= ce_n_i;
|
|
wr_n_r <= wr_n_i;
|
|
din_r <= unsigned(data_i);
|
|
-- Ready is very fast so the external CPU will see the signal
|
|
-- in time to extend the I/O operation.
|
|
ready_r <= ready_x;
|
|
end if;
|
|
end process;
|
|
|
|
-- Registered output.
|
|
ready_o <= ready_r;
|
|
|
|
|
|
-- -----------------------------------------------------------------------
|
|
--
|
|
-- External bus cycle FSM.
|
|
--
|
|
-- The SN76489 can only be written (registers cannot be read back), and
|
|
-- only when the chip is selected.
|
|
--
|
|
-- A write operation takes 32 clock cycles. A fast-IO mode is available
|
|
-- if legacy timing is not important.
|
|
|
|
bus_io : process
|
|
( ce_n_r, wr_n_r
|
|
, io_state_r, io_cnt_r, ready_r
|
|
) begin
|
|
|
|
io_state_x <= io_state_r;
|
|
io_cnt_x <= io_cnt_r;
|
|
ready_x <= ready_r;
|
|
en_reg_wr_s <= '0';
|
|
|
|
case io_state_r is
|
|
|
|
when IO_IDLE =>
|
|
-- Wait for CE_n and WR_n.
|
|
if ce_n_r = '0' and wr_n_r = '0' then
|
|
io_state_x <= IO_OP;
|
|
ready_x <= '0';
|
|
|
|
if FAST_IO_G = '1' then
|
|
-- No delay.
|
|
io_cnt_x <= (others => '0');
|
|
else
|
|
-- The real IC takes 32 cycles for an I/O operation.
|
|
io_cnt_x <= to_unsigned(31, io_cnt_x'length);
|
|
end if;
|
|
end if;
|
|
|
|
when IO_OP =>
|
|
if io_cnt_r = 0 then
|
|
io_state_x <= IO_WAIT;
|
|
en_reg_wr_s <= '1';
|
|
ready_x <= '1';
|
|
else
|
|
io_cnt_x <= io_cnt_r - 1;
|
|
end if;
|
|
|
|
when IO_WAIT =>
|
|
-- The CPU must end the write-cycle; fine if CE_n is still asserted.
|
|
if wr_n_r = '1' then
|
|
io_state_x <= IO_IDLE;
|
|
end if;
|
|
|
|
end case;
|
|
end process;
|
|
|
|
|
|
process
|
|
( clk_i, en_clk_psg_i
|
|
) begin
|
|
if rising_edge(clk_i) then
|
|
if en_clk_psg_i = '1' then
|
|
io_state_r <= io_state_x;
|
|
io_cnt_r <= io_cnt_x;
|
|
end if;
|
|
end if;
|
|
end process;
|
|
|
|
|
|
-- -----------------------------------------------------------------------
|
|
--
|
|
-- Registers. Setting the channel tone period requires two writes to set
|
|
-- the full 10-bit value. Bit numbering is n..0, which is *BACKWARDS* from
|
|
-- TI's bit numbering during this era.
|
|
--
|
|
-- 7654 3210
|
|
-- R0 1000 PPPP Channel A tone period 3..0.
|
|
-- 0-PP PPPP Channel A tone period 9..4.
|
|
-- R1 1001 AAAA Channel A attenuation.
|
|
-- R2 1010 PPPP Channel B tone period 3..0.
|
|
-- 0-PP PPPP Channel B tone period 9..4.
|
|
-- R3 1011 AAAA Channel B attenuation.
|
|
-- R4 1100 PPPP Channel C tone period 3..0.
|
|
-- 0-PP PPPP Channel C tone period 9..4.
|
|
-- R5 1101 AAAA Channel C attenuation.
|
|
-- R6 1110 -F-- Noise feedback, 0=periodic, 1=white noise
|
|
-- 1110 --SS Noise shift rate, 00=N/512, 01=N/1024, 10=N/2048, 11=Channel C
|
|
-- R7 1111 AAAA Noise attenuation.
|
|
|
|
-- The register last written is latched, so any bytes written with a '0' in
|
|
-- the MS-bit will go to the same register.
|
|
|
|
-- The output-level is converted to the equivalent DAC value and stored as
|
|
-- as the look-up result, rather than the 4-bit level index. This allows
|
|
-- sharing of the ROM lookup table, and uses less FPGA resources.
|
|
|
|
dac_level_s <= dacrom_ar(to_integer(unsigned(din_r(3 downto 0))));
|
|
|
|
register_file : process
|
|
( din_r, reg_addr_r, reg_sel_s, dac_level_s, en_reg_wr_s
|
|
, ch_a_period_r, ch_a_level_r
|
|
, ch_b_period_r, ch_b_level_r
|
|
, ch_c_period_r, ch_c_level_r
|
|
, noise_ctrl_r, noise_level_r, noise_shift_r
|
|
) begin
|
|
|
|
-- Register writes go to the specified register, data writes go to the
|
|
-- previously written register.
|
|
if din_r(7) = '0' then
|
|
reg_sel_s <= reg_addr_r;
|
|
else
|
|
reg_sel_s <= din_r(6 downto 4);
|
|
end if;
|
|
|
|
ch_a_period_x <= ch_a_period_r;
|
|
ch_a_level_x <= ch_a_level_r;
|
|
ch_b_period_x <= ch_b_period_r;
|
|
ch_b_level_x <= ch_b_level_r;
|
|
ch_c_period_x <= ch_c_period_r;
|
|
ch_c_level_x <= ch_c_level_r;
|
|
|
|
noise_ctrl_x <= noise_ctrl_r;
|
|
noise_shift_x <= noise_shift_r;
|
|
noise_level_x <= noise_level_r;
|
|
noise_rst_x <= '0';
|
|
|
|
|
|
case reg_sel_s is
|
|
|
|
when "000" =>
|
|
if din_r(7) = '0' then
|
|
ch_a_period_x <= din_r(5 downto 0) & ch_a_period_r(3 downto 0);
|
|
else
|
|
ch_a_period_x <= ch_a_period_r(9 downto 4) & din_r(3 downto 0);
|
|
end if;
|
|
|
|
when "001" =>
|
|
ch_a_level_x <= dac_level_s;
|
|
|
|
when "010" =>
|
|
if din_r(7) = '0' then
|
|
ch_b_period_x <= din_r(5 downto 0) & ch_b_period_r(3 downto 0);
|
|
else
|
|
ch_b_period_x <= ch_b_period_r(9 downto 4) & din_r(3 downto 0);
|
|
end if;
|
|
|
|
when "011" =>
|
|
ch_b_level_x <= dac_level_s;
|
|
|
|
when "100" =>
|
|
if din_r(7) = '0' then
|
|
ch_c_period_x <= din_r(5 downto 0) & ch_c_period_r(3 downto 0);
|
|
else
|
|
ch_c_period_x <= ch_c_period_r(9 downto 4) & din_r(3 downto 0);
|
|
end if;
|
|
|
|
when "101" =>
|
|
ch_c_level_x <= dac_level_s;
|
|
|
|
when "110" =>
|
|
noise_ctrl_x <= din_r(2);
|
|
noise_shift_x <= din_r(1 downto 0);
|
|
-- Writing to the noise control register resets the LFSR to its
|
|
-- initialization value.
|
|
noise_rst_x <= en_reg_wr_s;
|
|
|
|
-- "111"
|
|
when others =>
|
|
noise_level_x <= dac_level_s;
|
|
|
|
null;
|
|
end case;
|
|
end process;
|
|
|
|
|
|
process
|
|
( clk_i, en_clk_psg_i
|
|
) begin
|
|
if rising_edge(clk_i) then
|
|
if en_clk_psg_i = '1' then
|
|
|
|
noise_rst_r <= noise_rst_x;
|
|
|
|
if en_reg_wr_s = '1' then
|
|
-- Latch the register when the write specifies a register.
|
|
reg_addr_r <= reg_sel_s;
|
|
|
|
ch_a_period_r <= ch_a_period_x;
|
|
ch_a_level_r <= ch_a_level_x;
|
|
ch_b_period_r <= ch_b_period_x;
|
|
ch_b_level_r <= ch_b_level_x;
|
|
ch_c_period_r <= ch_c_period_x;
|
|
ch_c_level_r <= ch_c_level_x;
|
|
|
|
noise_ctrl_r <= noise_ctrl_x;
|
|
noise_shift_r <= noise_shift_x;
|
|
noise_level_r <= noise_level_x;
|
|
end if;
|
|
|
|
end if;
|
|
end if;
|
|
end process;
|
|
|
|
|
|
-- -----------------------------------------------------------------------
|
|
--
|
|
-- Clock conditioning. Reduce the input clock to provide the divide by
|
|
-- sixteen clock-phases.
|
|
--
|
|
|
|
clk_div16_x <= clk_div16_r + 1;
|
|
en_cnt_x <= '1' when clk_div16_r = 0 else '0';
|
|
|
|
process
|
|
( clk_i, en_clk_psg_i
|
|
) begin
|
|
if rising_edge(clk_i) then
|
|
if en_clk_psg_i = '1' then
|
|
clk_div16_r <= clk_div16_x;
|
|
en_cnt_r <= en_cnt_x;
|
|
end if;
|
|
end if;
|
|
end process;
|
|
|
|
|
|
-- -----------------------------------------------------------------------
|
|
--
|
|
-- Channel tone counters. The counters *always* count.
|
|
--
|
|
-- The counters count *down* and load the period when they reach zero. The
|
|
-- zero-check-and-load are all part of the same cycle. An implementation in
|
|
-- C might look like this:
|
|
--
|
|
-- {
|
|
-- if ( counter == 0 ) {
|
|
-- tone = !tone;
|
|
-- counter = period;
|
|
-- }
|
|
--
|
|
-- counter--;
|
|
-- }
|
|
--
|
|
-- With the period work-around described below:
|
|
--
|
|
-- {
|
|
-- if ( counter == 0 )
|
|
-- {
|
|
-- if ( period > 0 and period < 6 ) {
|
|
-- tone = 1;
|
|
-- } else {
|
|
-- tone = !tone;
|
|
-- }
|
|
--
|
|
-- counter = period;
|
|
-- }
|
|
--
|
|
-- counter--;
|
|
-- }
|
|
--
|
|
-- This also demonstrates why changing the tone period will not take effect
|
|
-- until the next cycle of the counter. Interestingly, the same counter is
|
|
-- used in the AY-3-8910 and YM-2149, only slightly modified to count up
|
|
-- (actually, in silicon both up and down counters are present
|
|
-- simultaneously) and reset on a >= period condition.
|
|
--
|
|
|
|
-- Amplitude Modulation.
|
|
--
|
|
-- With a typical 3.5MHz to 4.0MHz (max) input clock, the main-clock divide
|
|
-- by sixteen produces a 223.72KHz clock into the tone counters. With small
|
|
-- period counts, frequencies *well over* the human hearing range can be
|
|
-- produced.
|
|
--
|
|
-- The problem with the frequencies over 20KHz is, in a digital SoC the high
|
|
-- frequencies do not filter out like they do when the output is connected
|
|
-- to an external low-pass filter and inductive load (speaker), like they
|
|
-- are with the real IC.
|
|
--
|
|
-- In an all digital system with digital audio, the generated frequencies
|
|
-- should never be more than the Nyquist frequency (twice the sample rate).
|
|
-- Typical sample rates are 44.1KHz or 48KHz, so any frequency over 20KHz
|
|
-- should not be output (and is not audible to a human anyway).
|
|
--
|
|
-- The work-around here is to flat-line the toggle flip-flop for any tone
|
|
-- counter with a period that produces a frequency over the Nyquist rate.
|
|
-- This change still allows the technique of modulating the output with
|
|
-- rapid volume level changes.
|
|
--
|
|
-- Based on a typical PSG clock of 3.58MHz for a Z80-based system, the
|
|
-- period counts that cause frequencies above 20KHz are:
|
|
--
|
|
-- f = CLK / (32 * Count)
|
|
--
|
|
-- Clk 3,579,545Hz 2.793651ns
|
|
--
|
|
-- Cnt Frequency Period
|
|
-- 1 111860.78Hz 8.9396us
|
|
-- 2 55930.39Hz 17.8793us
|
|
-- 3 37286.92Hz 26.8190us
|
|
-- 4 27965.19Hz 35.7587us
|
|
-- 5 22372.15Hz 44.6984us
|
|
-- ---------------------------
|
|
-- 6 18643.46Hz 53.6381us First audible count.
|
|
-- 7 15980.11Hz 62.5777us
|
|
-- 8 13982.59Hz 71.5174us
|
|
-- 9 12428.97Hz 80.4571us
|
|
-- 10 11186.07Hz 89.3968us
|
|
-- 11 10169.16Hz 98.3365us
|
|
-- 12 9321.73Hz 107.2762us
|
|
-- 13 8604.67Hz 116.2158us
|
|
-- 14 7990.05Hz 125.1555us
|
|
-- 15 7457.38Hz 134.0952us
|
|
-- 16 6991.29Hz 143.0349us Used by some software for level-modulation.
|
|
-- ---------------------------
|
|
-- 17 6580.04Hz 151.9746us
|
|
-- ...
|
|
-- 0 109.23Hz 9.1542ms A count of zero is the maximum period.
|
|
--
|
|
-- While a count of 6 is technically the Nyquist cut-off, some game software
|
|
-- is known to use a period of 16 as the base frequency for the amplitude
|
|
-- modulation hack. While audible, the 7KHz tone is not very musical, and
|
|
-- causes a harsh (if not painful) undertone to the sound being created with
|
|
-- the amplitude modulation.
|
|
--
|
|
-- Suffice to say, setting the flat-line cut-off to 16 should not affect the
|
|
-- audio for most software in any negative way, but can help some software
|
|
-- sound better.
|
|
|
|
|
|
-- A channel counter and tone flip-flop.
|
|
ch_a_cnt_x <=
|
|
-- Load uses count-enable next-state look-ahead when counter is 0.
|
|
ch_a_period_r when (en_cnt_x = '1' and ch_a_cnt_r = 0) else
|
|
-- Counting uses the current-state.
|
|
ch_a_cnt_r - 1 when en_cnt_r = '1' else
|
|
ch_a_cnt_r;
|
|
|
|
flatline_a_s <=
|
|
'1' when ch_a_period_r > 0 and ch_a_period_r < MIN_PERIOD_CNT_G else
|
|
'0';
|
|
|
|
tone_a_x <=
|
|
-- Flat-line the output for counts that produce frequencies > 20KHz.
|
|
'1' when flatline_a_s = '1' else
|
|
-- Toggle channel tone flip-flop when the counter reaches 0.
|
|
-- Same look-ahead condition as the counter-load.
|
|
not tone_a_r when (en_cnt_x = '1' and ch_a_cnt_r = 0) else
|
|
tone_a_r;
|
|
|
|
-- B channel counter and tone flip-flop.
|
|
ch_b_cnt_x <=
|
|
ch_b_period_r when (en_cnt_x = '1' and ch_b_cnt_r = 0) else
|
|
ch_b_cnt_r - 1 when en_cnt_r = '1' else
|
|
ch_b_cnt_r;
|
|
|
|
flatline_b_s <=
|
|
'1' when ch_b_period_r > 0 and ch_b_period_r < MIN_PERIOD_CNT_G else
|
|
'0';
|
|
|
|
tone_b_x <=
|
|
'1' when flatline_b_s = '1' else
|
|
not tone_b_r when (en_cnt_x = '1' and ch_b_cnt_r = 0) else
|
|
tone_b_r;
|
|
|
|
-- C channel counter and tone flip-flop.
|
|
ch_c_cnt_x <=
|
|
ch_c_period_r when (en_cnt_x = '1' and ch_c_cnt_r = 0) else
|
|
ch_c_cnt_r - 1 when en_cnt_r = '1' else
|
|
ch_c_cnt_r;
|
|
|
|
flatline_c_s <=
|
|
'1' when ch_c_period_r > 0 and ch_c_period_r < MIN_PERIOD_CNT_G else
|
|
'0';
|
|
|
|
tone_c_x <= flatline_c_s or c_ff_r;
|
|
|
|
-- The work-around to limit high frequency outputs interferes with Channel-C
|
|
-- being able to clock the noise shift register. This is a work-around to
|
|
-- that work-around, to always have an output from Channel-C that can be
|
|
-- used to clock the noise shift register.
|
|
c_ff_x <=
|
|
not c_ff_r when (en_cnt_x = '1' and ch_c_cnt_r = 0) else
|
|
c_ff_r;
|
|
|
|
|
|
process
|
|
( clk_i, en_clk_psg_i
|
|
) begin
|
|
if rising_edge(clk_i) then
|
|
|
|
if en_clk_psg_i = '1' then
|
|
ch_a_cnt_r <= ch_a_cnt_x;
|
|
tone_a_r <= tone_a_x;
|
|
|
|
ch_b_cnt_r <= ch_b_cnt_x;
|
|
tone_b_r <= tone_b_x;
|
|
|
|
ch_c_cnt_r <= ch_c_cnt_x;
|
|
tone_c_r <= tone_c_x;
|
|
c_ff_r <= c_ff_x;
|
|
end if;
|
|
|
|
end if;
|
|
end process;
|
|
|
|
|
|
-- -----------------------------------------------------------------------
|
|
--
|
|
-- Noise period counter. A continuous counter to further divide the input
|
|
-- clock by 32, 64, or 128. The output goes to a selector, controlled by the
|
|
-- noise register, to choose one of the three rates, or the output flip-flop
|
|
-- of channel C, to drive the LFSR.
|
|
|
|
noise_cnt_x <=
|
|
noise_cnt_r + 1 when en_cnt_r = '1' else
|
|
noise_cnt_r;
|
|
|
|
with noise_shift_r select
|
|
noise_ff_x <= -- N = PSG input clock, typical 3.58MHz.
|
|
noise_cnt_r(4) when "00", -- N / 512 = approx 6991.2988Hz 143.034us
|
|
noise_cnt_r(5) when "01", -- N / 1024 = approx 3495.6494Hz 286.069us
|
|
noise_cnt_r(6) when "10", -- N / 2048 = approx 1747.8247Hz 572.139us
|
|
c_ff_r when others; -- "11" -- Channel C tone as the clock.
|
|
|
|
|
|
-- The noise can be periodic or white depending on the feedback-bit in the
|
|
-- noise control register. 0 = periodic, which just disables the XOR of the
|
|
-- LFSR and loops the single initialization bit through the shift register.
|
|
--
|
|
-- Noise 15-bit right-shift LFSR with taps at 0 and 1, LS-bit is the output.
|
|
-- Reset loads the LFSR with 0x4000 to prevent lock-up. The same pattern
|
|
-- can be obtained with a left-shift, taps at 13 and 14, MS-bit output.
|
|
--
|
|
-- Accurate implementation in C, no branching:
|
|
--
|
|
-- uint32_t lfsr;
|
|
-- uint8_t noise_bit;
|
|
--
|
|
-- // A 15-input NOR gate ensures init with 100_0000_0000_0000
|
|
-- lfsr = (1 << 14);
|
|
--
|
|
-- // Taps at 0 and 1, mask result.
|
|
-- int32_t fb = ((lfsr >> 0) ^ (lfsr >> 1)) & 1;
|
|
--
|
|
-- // Right-shift, feedback bit to the most-significant bit.
|
|
-- lfsr = (lfsr >> 1) | (fb << 14);
|
|
--
|
|
-- noise_bit = (lfsr & 1);
|
|
--
|
|
|
|
noise_fb_s <=
|
|
(noise_ctrl_r and noise_lfsr_r(1)) xor noise_lfsr_r(0);
|
|
noise_lfsr_x <= noise_fb_s & noise_lfsr_r(14 downto 1);
|
|
noise_s <= noise_lfsr_r(0);
|
|
|
|
|
|
process
|
|
( clk_i, en_clk_psg_i, noise_rst_r
|
|
) begin
|
|
if rising_edge(clk_i) then
|
|
|
|
-- ** NOTE: This reset is active high.
|
|
if noise_rst_r = '1' then
|
|
noise_cnt_r <= (others => '0');
|
|
noise_ff_r <= '1';
|
|
noise_lfsr_r <= b"100_0000_0000_0000";
|
|
|
|
elsif en_clk_psg_i = '1' then
|
|
noise_cnt_r <= noise_cnt_x;
|
|
noise_ff_r <= noise_ff_x;
|
|
-- Look-ahead rising-edge detect the noise flip-flop.
|
|
if noise_ff_r = '0' and noise_ff_x = '1' then
|
|
noise_lfsr_r <= noise_lfsr_x;
|
|
end if;
|
|
end if;
|
|
|
|
end if;
|
|
end process;
|
|
|
|
|
|
-- -----------------------------------------------------------------------
|
|
--
|
|
-- Amplitude / Attenuation control. The amplitude of each channel is
|
|
-- controlled by the channel's 4-bit attenuation register.
|
|
--
|
|
-- The "level" in the SN76489 is an amount of attenuation, and the channel's
|
|
-- level has already been converted into its DAC level. When the tone
|
|
-- flip-flop is '0', the most attenuation (minimum DAC level) is output.
|
|
|
|
level_a_s <=
|
|
(others => '0') when tone_a_r = '0' else
|
|
ch_a_level_r;
|
|
|
|
level_b_s <=
|
|
(others => '0') when tone_b_r = '0' else
|
|
ch_b_level_r;
|
|
|
|
level_c_s <=
|
|
(others => '0') when tone_c_r = '0' else
|
|
ch_c_level_r;
|
|
|
|
level_n_s <=
|
|
(others => '0') when noise_s = '0' else
|
|
noise_level_r;
|
|
|
|
|
|
-- -----------------------------------------------------------------------
|
|
--
|
|
-- Output registers and unsigned summation. If the level had not already
|
|
-- been converted to the output level, this would also be the DAC section.
|
|
--
|
|
|
|
ch_a_o <= dac_a_r;
|
|
ch_b_o <= dac_b_r;
|
|
ch_c_o <= dac_c_r;
|
|
noise_o <= dac_n_r;
|
|
mix_audio_o <= sum_audio_r;
|
|
|
|
process
|
|
( clk_i, en_clk_psg_i
|
|
) begin
|
|
if rising_edge(clk_i) then
|
|
if en_clk_psg_i = '1' then
|
|
|
|
dac_a_r <= level_a_s;
|
|
dac_b_r <= level_b_s;
|
|
dac_c_r <= level_c_s;
|
|
dac_n_r <= level_n_s;
|
|
|
|
-- Sum the audio channels.
|
|
sum_audio_r <= ("00" & level_a_s) + ("00" & level_b_s) +
|
|
("00" & level_c_s) + ("00" & level_n_s);
|
|
end if;
|
|
end if;
|
|
end process;
|
|
|
|
|
|
-- -----------------------------------------------------------------------
|
|
--
|
|
-- Signed zero-centered 14-bit PCM.
|
|
--
|
|
|
|
-- Make a -/+ level value depending on the tone state. When the flat-line
|
|
-- work-around for frequencies over the Nyquist rate is in effect, adjust
|
|
-- the unsigned level-range to a signed-level range.
|
|
--
|
|
-- signed_level =
|
|
-- level - (range / 2) when flat-line
|
|
--
|
|
-- otherwise, -/+ zero-centered square-wave:
|
|
--
|
|
-- signed_level =
|
|
-- -(level / 2) when tone == 0
|
|
-- (level / 2) when tone == 1
|
|
|
|
sign_a_x <=
|
|
ch_a_level_r - x"800" when flatline_a_s = '1' else
|
|
("1" & (not ch_a_level_r(11 downto 1))) + 1 when tone_a_r = '0' else
|
|
("0" & ch_a_level_r(11 downto 1));
|
|
|
|
sign_b_x <=
|
|
ch_b_level_r - x"800" when flatline_b_s = '1' else
|
|
("1" & (not ch_b_level_r(11 downto 1))) + 1 when tone_b_r = '0' else
|
|
("0" & ch_b_level_r(11 downto 1));
|
|
|
|
sign_c_x <=
|
|
ch_c_level_r - x"800" when flatline_c_s = '1' else
|
|
("1" & (not ch_c_level_r(11 downto 1))) + 1 when tone_c_r = '0' else
|
|
("0" & ch_c_level_r(11 downto 1));
|
|
|
|
sign_n_x <=
|
|
("1" & (not noise_level_r(11 downto 1))) + 1 when noise_s = '0' else
|
|
("0" & noise_level_r(11 downto 1));
|
|
|
|
|
|
pcm14s_o <= pcm14s_r;
|
|
|
|
|
|
process
|
|
( clk_i, en_clk_psg_i
|
|
) begin
|
|
if rising_edge(clk_i) then
|
|
if en_clk_psg_i = '1' then
|
|
|
|
sign_a_r <= sign_a_x;
|
|
sign_b_r <= sign_b_x;
|
|
sign_c_r <= sign_c_x;
|
|
sign_n_r <= sign_n_x;
|
|
|
|
-- Sum to signed 14-bit and left-align to signed 16-bit.
|
|
pcm14s_r <=
|
|
(sign_a_r(11) & sign_a_r(11) & sign_a_r) +
|
|
(sign_b_r(11) & sign_b_r(11) & sign_b_r) +
|
|
(sign_c_r(11) & sign_c_r(11) & sign_c_r) +
|
|
(sign_n_r(11) & sign_n_r(11) & sign_n_r);
|
|
|
|
end if;
|
|
end if;
|
|
end process;
|
|
|
|
end rtl;
|