mirror of
https://github.com/Gehstock/Mist_FPGA.git
synced 2026-05-01 06:09:05 +00:00
657 lines
28 KiB
VHDL
657 lines
28 KiB
VHDL
-------------------------------------------------------------------------------
|
|
--
|
|
-- SID 6581 (voice)
|
|
--
|
|
-- This piece of VHDL code describes a single SID voice (sound channel)
|
|
--
|
|
-------------------------------------------------------------------------------
|
|
-- to do: - better resolution of result signal voice, this is now only 12bits
|
|
-- but it could be 20 !! Problem, it does not fit the PWM-dac
|
|
-------------------------------------------------------------------------------
|
|
|
|
library IEEE;
|
|
use IEEE.std_logic_1164.all;
|
|
--use IEEE.std_logic_arith.all;
|
|
use IEEE.std_logic_unsigned.all;
|
|
use IEEE.numeric_std.all;
|
|
|
|
-------------------------------------------------------------------------------
|
|
|
|
entity sid_voice is
|
|
port (
|
|
clk_1MHz : in std_logic; -- this line drives the oscilator
|
|
reset : in std_logic; -- active high signal (i.e. registers are reset when reset=1)
|
|
Freq_lo : in std_logic_vector(7 downto 0); -- low-byte of frequency register
|
|
Freq_hi : in std_logic_vector(7 downto 0); -- high-byte of frequency register
|
|
Pw_lo : in std_logic_vector(7 downto 0); -- low-byte of PuleWidth register
|
|
Pw_hi : in std_logic_vector(3 downto 0); -- high-nibble of PuleWidth register
|
|
Control : in std_logic_vector(7 downto 0); -- control register
|
|
Att_dec : in std_logic_vector(7 downto 0); -- attack-deccay register
|
|
Sus_Rel : in std_logic_vector(7 downto 0); -- sustain-release register
|
|
PA_MSB_in : in std_logic; -- Phase Accumulator MSB input
|
|
PA_MSB_out : out std_logic; -- Phase Accumulator MSB output
|
|
Osc : out std_logic_vector(7 downto 0); -- Voice waveform register
|
|
Env : out std_logic_vector(7 downto 0); -- Voice envelope register
|
|
voice : out std_logic_vector(11 downto 0) -- Voice waveform, this is the actual audio signal
|
|
);
|
|
end sid_voice;
|
|
|
|
architecture Behavioral of sid_voice is
|
|
|
|
-------------------------------------------------------------------------------
|
|
-- Altera multiplier
|
|
-- COMPONENT lpm_mult
|
|
-- GENERIC
|
|
-- (
|
|
-- lpm_hint : STRING;
|
|
-- lpm_representation : STRING;
|
|
-- lpm_type : STRING;
|
|
-- lpm_widtha : NATURAL;
|
|
-- lpm_widthb : NATURAL;
|
|
-- lpm_widthp : NATURAL;
|
|
-- lpm_widths : NATURAL
|
|
-- );
|
|
-- PORT
|
|
-- (
|
|
-- dataa : IN STD_LOGIC_VECTOR (11 DOWNTO 0);
|
|
-- datab : IN STD_LOGIC_VECTOR (7 DOWNTO 0);
|
|
-- result : OUT STD_LOGIC_VECTOR (19 DOWNTO 0)
|
|
-- );
|
|
-- END COMPONENT;
|
|
|
|
-------------------------------------------------------------------------------
|
|
|
|
signal accumulator : std_logic_vector(23 downto 0) := (others => '0');
|
|
signal accu_bit_prev : std_logic := '0';
|
|
signal PA_MSB_in_prev : std_logic := '0';
|
|
|
|
-- this type of signal has only two states 0 or 1 (so no more bits are required)
|
|
signal pulse : std_logic := '0';
|
|
signal sawtooth : std_logic_vector(11 downto 0) := (others => '0');
|
|
signal triangle : std_logic_vector(11 downto 0) := (others => '0');
|
|
signal noise : std_logic_vector(11 downto 0) := (others => '0');
|
|
signal LFSR : std_logic_vector(22 downto 0) := (others => '0');
|
|
|
|
signal frequency : std_logic_vector(15 downto 0) := (others => '0');
|
|
signal pulsewidth : std_logic_vector(11 downto 0) := (others => '0');
|
|
|
|
-- Envelope Generator
|
|
type envelope_state_types is (idle, attack, attack_lp, decay, decay_lp, sustain, releases, release_lp);
|
|
signal cur_state, next_state : envelope_state_types;
|
|
signal divider_value : integer range 0 to 2**15 - 1 :=0;
|
|
signal divider_attack : integer range 0 to 2**15 - 1 :=0;
|
|
signal divider_dec_rel : integer range 0 to 2**15 - 1 :=0;
|
|
signal divider_counter : integer range 0 to 2**18 - 1 :=0;
|
|
signal exp_table_value : integer range 0 to 2**18 - 1 :=0;
|
|
signal exp_table_active : std_logic := '0';
|
|
signal divider_rst : std_logic := '0';
|
|
signal Dec_rel : std_logic_vector(3 downto 0) := (others => '0');
|
|
signal Dec_rel_sel : std_logic := '0';
|
|
|
|
signal env_counter : std_logic_vector(17 downto 0) := (others => '0');
|
|
signal env_count_hold_A : std_logic := '0';
|
|
signal env_count_hold_B : std_logic := '0';
|
|
signal env_cnt_up : std_logic := '0';
|
|
signal env_cnt_clear : std_logic := '0';
|
|
|
|
signal signal_mux : std_logic_vector(17 downto 0) := (others => '0');
|
|
signal signal_vol : std_logic_vector(35 downto 0) := (others => '0');
|
|
|
|
-------------------------------------------------------------------------------------
|
|
|
|
-- stop the oscillator when test = '1'
|
|
alias test : std_logic is Control(3);
|
|
-- Ring Modulation was accomplished by substituting the accumulator MSB of an
|
|
-- oscillator in the EXOR function of the triangle waveform generator with the
|
|
-- accumulator MSB of the previous oscillator. That is why the triangle waveform
|
|
-- must be selected to use Ring Modulation.
|
|
alias ringmod : std_logic is Control(2);
|
|
-- Hard Sync was accomplished by clearing the accumulator of an Oscillator
|
|
-- based on the accumulator MSB of the previous oscillator.
|
|
alias sync : std_logic is Control(1);
|
|
--
|
|
alias gate : std_logic is Control(0);
|
|
|
|
-------------------------------------------------------------------------------------
|
|
|
|
begin
|
|
|
|
-- output the Phase accumulator's MSB for sync and ringmod purposes
|
|
PA_MSB_out <= accumulator(23);
|
|
-- output the upper 8-bits of the waveform.
|
|
-- Useful for random numbers (noise must be selected)
|
|
Osc <= signal_mux(11 downto 4);
|
|
-- output the envelope register, for special sound effects when connecting this
|
|
-- signal to the input of other channels/voices
|
|
Env <= env_counter(7 downto 0);
|
|
-- use the register value to fill the variable
|
|
frequency(15 downto 8) <= Freq_hi(7 downto 0);
|
|
--
|
|
frequency(7 downto 0) <= Freq_lo(7 downto 0);
|
|
-- use the register value to fill the variable
|
|
pulsewidth(11 downto 8) <= Pw_hi(3 downto 0);
|
|
--
|
|
pulsewidth(7 downto 0) <= Pw_lo(7 downto 0);
|
|
--
|
|
voice <= signal_vol(19 downto 8);
|
|
|
|
-- Phase accumulator :
|
|
-- "As I recall, the Oscillator is a 24-bit phase-accumulating design of which
|
|
-- the lower 16-bits are programmable for pitch control. The output of the
|
|
-- accumulator goes directly to a D/A converter through a waveform selector.
|
|
-- Normally, the output of a phase-accumulating oscillator would be used as an
|
|
-- address into memory which contained a wavetable, but SID had to be entirely
|
|
-- self-contained and there was no room at all for a wavetable on the chip."
|
|
-- "Hard Sync was accomplished by clearing the accumulator of an Oscillator
|
|
-- based on the accumulator MSB of the previous oscillator."
|
|
PhaseAcc:process(clk_1MHz)
|
|
begin
|
|
if (rising_edge(clk_1MHz)) then
|
|
PA_MSB_in_prev <= PA_MSB_in;
|
|
-- the reset and test signal can stop the oscillator,
|
|
-- stopping the oscillator is very useful when you want to play "samples"
|
|
if ((reset = '1') or (test = '1') or ((sync = '1') and (PA_MSB_in_prev /= PA_MSB_in) and (PA_MSB_in = '0'))) then
|
|
accumulator <= (others => '0');
|
|
else
|
|
-- accumulate the new phase (i.o.w. increment env_counter with the freq. value)
|
|
accumulator <= accumulator + ("0" & frequency(15 downto 0));
|
|
end if;
|
|
end if;
|
|
end process;
|
|
|
|
-- Sawtooth waveform :
|
|
-- "The Sawtooth waveform was created by sending the upper 12-bits of the
|
|
-- accumulator to the 12-bit Waveform D/A."
|
|
Snd_Sawtooth:process(clk_1MHz)
|
|
begin
|
|
if (rising_edge(clk_1MHz)) then
|
|
sawtooth <= accumulator(23 downto 12);
|
|
end if;
|
|
end process;
|
|
|
|
--Pulse waveform :
|
|
-- "The Pulse waveform was created by sending the upper 12-bits of the
|
|
-- accumulator to a 12-bit digital comparator. The output of the comparator was
|
|
-- either a one or a zero. This single output was then sent to all 12 bits of
|
|
-- the Waveform D/A. "
|
|
Snd_pulse:process(clk_1MHz)
|
|
begin
|
|
if (rising_edge(clk_1MHz)) then
|
|
if ((accumulator(23 downto 12)) >= (pulsewidth(11 downto 0))) then
|
|
pulse <= '1';
|
|
else
|
|
pulse <= '0';
|
|
end if;
|
|
end if;
|
|
end process;
|
|
|
|
--Triangle waveform :
|
|
-- "The Triangle waveform was created by using the MSB of the accumulator to
|
|
-- invert the remaining upper 11 accumulator bits using EXOR gates. These 11
|
|
-- bits were then left-shifted (throwing away the MSB) and sent to the Waveform
|
|
-- D/A (so the resolution of the triangle waveform was half that of the sawtooth,
|
|
-- but the amplitude and frequency were the same). "
|
|
-- "Ring Modulation was accomplished by substituting the accumulator MSB of an
|
|
-- oscillator in the EXOR function of the triangle waveform generator with the
|
|
-- accumulator MSB of the previous oscillator. That is why the triangle waveform
|
|
-- must be selected to use Ring Modulation."
|
|
Snd_triangle:process(clk_1MHz)
|
|
begin
|
|
if (rising_edge(clk_1MHz)) then
|
|
if ringmod = '0' then
|
|
-- no ringmodulation
|
|
triangle(11)<= accumulator(23) xor accumulator(22);
|
|
triangle(10)<= accumulator(23) xor accumulator(21);
|
|
triangle(9) <= accumulator(23) xor accumulator(20);
|
|
triangle(8) <= accumulator(23) xor accumulator(19);
|
|
triangle(7) <= accumulator(23) xor accumulator(18);
|
|
triangle(6) <= accumulator(23) xor accumulator(17);
|
|
triangle(5) <= accumulator(23) xor accumulator(16);
|
|
triangle(4) <= accumulator(23) xor accumulator(15);
|
|
triangle(3) <= accumulator(23) xor accumulator(14);
|
|
triangle(2) <= accumulator(23) xor accumulator(13);
|
|
triangle(1) <= accumulator(23) xor accumulator(12);
|
|
triangle(0) <= accumulator(23) xor accumulator(11);
|
|
else
|
|
-- ringmodulation by the other voice (previous voice)
|
|
triangle(11)<= PA_MSB_in xor accumulator(22);
|
|
triangle(10)<= PA_MSB_in xor accumulator(21);
|
|
triangle(9) <= PA_MSB_in xor accumulator(20);
|
|
triangle(8) <= PA_MSB_in xor accumulator(19);
|
|
triangle(7) <= PA_MSB_in xor accumulator(18);
|
|
triangle(6) <= PA_MSB_in xor accumulator(17);
|
|
triangle(5) <= PA_MSB_in xor accumulator(16);
|
|
triangle(4) <= PA_MSB_in xor accumulator(15);
|
|
triangle(3) <= PA_MSB_in xor accumulator(14);
|
|
triangle(2) <= PA_MSB_in xor accumulator(13);
|
|
triangle(1) <= PA_MSB_in xor accumulator(12);
|
|
triangle(0) <= PA_MSB_in xor accumulator(11);
|
|
end if;
|
|
end if;
|
|
end process;
|
|
|
|
--Noise (23-bit Linear Feedback Shift Register, max combinations = 8388607) :
|
|
-- "The Noise waveform was created using a 23-bit pseudo-random sequence
|
|
-- generator (i.e., a shift register with specific outputs fed back to the input
|
|
-- through combinatorial logic). The shift register was clocked by one of the
|
|
-- intermediate bits of the accumulator to keep the frequency content of the
|
|
-- noise waveform relatively the same as the pitched waveforms.
|
|
-- The upper 12-bits of the shift register were sent to the Waveform D/A."
|
|
noise <= LFSR(22 downto 11);
|
|
|
|
Snd_noise:process(clk_1MHz)
|
|
begin
|
|
if (rising_edge(clk_1MHz)) then
|
|
-- the test signal can stop the oscillator,
|
|
-- stopping the oscillator is very useful when you want to play "samples"
|
|
if ((reset = '1') or (test = '1')) then
|
|
accu_bit_prev <= '0';
|
|
-- the "seed" value (the value that eventually determines the output
|
|
-- pattern) may never be '0' otherwise the generator "locks up"
|
|
LFSR <= "00000000000000000000001";
|
|
else
|
|
accu_bit_prev <= accumulator(22);
|
|
-- when not equal to ...
|
|
if (accu_bit_prev /= accumulator(22)) then
|
|
LFSR(22 downto 1) <= LFSR(21 downto 0);
|
|
LFSR(0) <= LFSR(17) xor LFSR(22); -- see Xilinx XAPP052 for maximal LFSR taps
|
|
else
|
|
LFSR <= LFSR;
|
|
end if;
|
|
end if;
|
|
end if;
|
|
end process;
|
|
|
|
-- Waveform Output selector (MUX):
|
|
-- "Since all of the waveforms were just digital bits, the Waveform Selector
|
|
-- consisted of multiplexers that selected which waveform bits would be sent
|
|
-- to the Waveform D/A. The multiplexers were single transistors and did not
|
|
-- provide a "lock-out", allowing combinations of the waveforms to be selected.
|
|
-- The combination was actually a logical ANDing of the bits of each waveform,
|
|
-- which produced unpredictable results, so I didn't encourage this, especially
|
|
-- since it could lock up the pseudo-random sequence generator by filling it
|
|
-- with zeroes."
|
|
Snd_select:process(clk_1MHz)
|
|
begin
|
|
if (rising_edge(clk_1MHz)) then
|
|
signal_mux(11) <= (triangle(11) and Control(4)) or (sawtooth(11) and Control(5)) or (pulse and Control(6)) or (noise(11) and Control(7));
|
|
signal_mux(10) <= (triangle(10) and Control(4)) or (sawtooth(10) and Control(5)) or (pulse and Control(6)) or (noise(10) and Control(7));
|
|
signal_mux(9) <= (triangle(9) and Control(4)) or (sawtooth(9) and Control(5)) or (pulse and Control(6)) or (noise(9) and Control(7));
|
|
signal_mux(8) <= (triangle(8) and Control(4)) or (sawtooth(8) and Control(5)) or (pulse and Control(6)) or (noise(8) and Control(7));
|
|
signal_mux(7) <= (triangle(7) and Control(4)) or (sawtooth(7) and Control(5)) or (pulse and Control(6)) or (noise(7) and Control(7));
|
|
signal_mux(6) <= (triangle(6) and Control(4)) or (sawtooth(6) and Control(5)) or (pulse and Control(6)) or (noise(6) and Control(7));
|
|
signal_mux(5) <= (triangle(5) and Control(4)) or (sawtooth(5) and Control(5)) or (pulse and Control(6)) or (noise(5) and Control(7));
|
|
signal_mux(4) <= (triangle(4) and Control(4)) or (sawtooth(4) and Control(5)) or (pulse and Control(6)) or (noise(4) and Control(7));
|
|
signal_mux(3) <= (triangle(3) and Control(4)) or (sawtooth(3) and Control(5)) or (pulse and Control(6)) or (noise(3) and Control(7));
|
|
signal_mux(2) <= (triangle(2) and Control(4)) or (sawtooth(2) and Control(5)) or (pulse and Control(6)) or (noise(2) and Control(7));
|
|
signal_mux(1) <= (triangle(1) and Control(4)) or (sawtooth(1) and Control(5)) or (pulse and Control(6)) or (noise(1) and Control(7));
|
|
signal_mux(0) <= (triangle(0) and Control(4)) or (sawtooth(0) and Control(5)) or (pulse and Control(6)) or (noise(0) and Control(7));
|
|
end if;
|
|
end process;
|
|
|
|
-- Waveform envelope (volume) control :
|
|
-- "The output of the Waveform D/A (which was an analog voltage at this point)
|
|
-- was fed into the reference input of an 8-bit multiplying D/A, creating a DCA
|
|
-- (digitally-controlled-amplifier). The digital control word which modulated
|
|
-- the amplitude of the waveform came from the Envelope Generator."
|
|
-- "The 8-bit output of the Envelope Generator was then sent to the Multiplying
|
|
-- D/A converter to modulate the amplitude of the selected Oscillator Waveform
|
|
-- (to be technically accurate, actually the waveform was modulating the output
|
|
-- of the Envelope Generator, but the result is the same)."
|
|
Envelope_multiplier:process(clk_1MHz)
|
|
begin
|
|
if (rising_edge(clk_1MHz)) then
|
|
--calculate the resulting volume (due to the envelope generator) of the
|
|
--voice, signal_mux(12bit) * env_counter(8bit), so the result will
|
|
--require 20 bits !!
|
|
signal_vol <= signal_mux * env_counter;
|
|
end if;
|
|
end process;
|
|
|
|
-- Altera multiplier
|
|
-- lpm_mult_component : lpm_mult
|
|
-- GENERIC MAP
|
|
-- (
|
|
-- lpm_hint => "MAXIMIZE_SPEED=5",
|
|
-- lpm_representation => "UNSIGNED",
|
|
-- lpm_type => "LPM_MULT",
|
|
-- lpm_widtha => 12,
|
|
-- lpm_widthb => 8,
|
|
-- lpm_widthp => 20,
|
|
-- lpm_widths => 1
|
|
-- )
|
|
-- PORT MAP
|
|
-- (
|
|
-- dataa(11 downto 0) => signal_mux,
|
|
-- datab(7 downto 0) => env_counter,
|
|
-- result => signal_vol
|
|
-- );
|
|
|
|
-- Envelope generator :
|
|
-- "The Envelope Generator was simply an 8-bit up/down counter which, when
|
|
-- triggered by the Gate bit, counted from 0 to 255 at the Attack rate, from
|
|
-- 255 down to the programmed Sustain value at the Decay rate, remained at the
|
|
-- Sustain value until the Gate bit was cleared then counted down from the
|
|
-- Sustain value to 0 at the Release rate."
|
|
--
|
|
-- /\
|
|
-- / \
|
|
-- / | \________
|
|
-- / | | \
|
|
-- / | | |\
|
|
-- / | | | \
|
|
-- attack|dec|sustain|rel
|
|
|
|
-- this process controls the state machine "current-state"-value
|
|
Envelope_SM_advance: process (reset, clk_1MHz)
|
|
begin
|
|
if (reset = '1') then
|
|
cur_state <= idle;
|
|
else
|
|
if (rising_edge(clk_1MHz)) then
|
|
cur_state <= next_state;
|
|
end if;
|
|
end if;
|
|
end process;
|
|
|
|
|
|
-- this process controls the envelope (in other words, the volume control)
|
|
Envelope_SM: process (reset, cur_state, gate, divider_attack, divider_dec_rel, Att_dec, Sus_Rel, env_counter)
|
|
begin
|
|
if (reset = '1') then
|
|
next_state <= idle;
|
|
env_cnt_clear <='1';
|
|
env_cnt_up <='1';
|
|
env_count_hold_B <='1';
|
|
divider_rst <='1';
|
|
divider_value <= 0;
|
|
exp_table_active <='0';
|
|
Dec_rel_sel <='0'; -- select decay as input for decay/release table
|
|
else
|
|
env_cnt_clear <='0'; -- use this statement unless stated otherwise
|
|
env_cnt_up <='1'; -- use this statement unless stated otherwise
|
|
env_count_hold_B <='1'; -- use this statement unless stated otherwise
|
|
divider_rst <='0'; -- use this statement unless stated otherwise
|
|
divider_value <= 0; -- use this statement unless stated otherwise
|
|
exp_table_active <='0'; -- use this statement unless stated otherwise
|
|
case cur_state is
|
|
|
|
-- IDLE
|
|
when idle =>
|
|
env_cnt_clear <= '1'; -- clear envelope env_counter
|
|
divider_rst <= '1';
|
|
Dec_rel_sel <= '0'; -- select decay as input for decay/release table
|
|
if gate = '1' then
|
|
next_state <= attack;
|
|
else
|
|
next_state <= idle;
|
|
end if;
|
|
|
|
when attack =>
|
|
env_cnt_clear <= '1'; -- clear envelope env_counter
|
|
divider_rst <= '1';
|
|
divider_value <= divider_attack;
|
|
next_state <= attack_lp;
|
|
Dec_rel_sel <= '0'; -- select decay as input for decay/release table
|
|
|
|
when attack_lp =>
|
|
env_count_hold_B <= '0'; -- enable envelope env_counter
|
|
env_cnt_up <= '1'; -- envelope env_counter must count up (increment)
|
|
divider_value <= divider_attack;
|
|
Dec_rel_sel <= '0'; -- select decay as input for decay/release table
|
|
if env_counter = "11111111" then
|
|
next_state <= decay;
|
|
else
|
|
if gate = '0' then
|
|
next_state <= releases;
|
|
else
|
|
next_state <= attack_lp;
|
|
end if;
|
|
end if;
|
|
|
|
when decay =>
|
|
divider_rst <= '1';
|
|
exp_table_active <= '1'; -- activate exponential look-up table
|
|
env_cnt_up <= '0'; -- envelope env_counter must count down (decrement)
|
|
divider_value <= divider_dec_rel;
|
|
next_state <= decay_lp;
|
|
Dec_rel_sel <= '0'; -- select decay as input for decay/release table
|
|
|
|
when decay_lp =>
|
|
exp_table_active <= '1'; -- activate exponential look-up table
|
|
env_count_hold_B <= '0'; -- enable envelope env_counter
|
|
env_cnt_up <= '0'; -- envelope env_counter must count down (decrement)
|
|
divider_value <= divider_dec_rel;
|
|
Dec_rel_sel <= '0'; -- select decay as input for decay/release table
|
|
if (env_counter(7 downto 4) = Sus_Rel(7 downto 4)) then
|
|
next_state <= sustain;
|
|
else
|
|
if gate = '0' then
|
|
next_state <= releases;
|
|
else
|
|
next_state <= decay_lp;
|
|
end if;
|
|
end if;
|
|
|
|
-- "A digital comparator was used for the Sustain function. The upper
|
|
-- four bits of the Up/Down counter were compared to the programmed
|
|
-- Sustain value and would stop the clock to the Envelope Generator when
|
|
-- the counter counted down to the Sustain value. This created 16 linearly
|
|
-- spaced sustain levels without havingto go through a look-up table
|
|
-- translation between the 4-bit register value and the 8-bit Envelope
|
|
-- Generator output. It also meant that sustain levels were adjustable
|
|
-- in steps of 16. Again, more register bits would have provided higher
|
|
-- resolution."
|
|
-- "When the Gate bit was cleared, the clock would again be enabled,
|
|
-- allowing the counter to count down to zero. Like an analog envelope
|
|
-- generator, the SID Envelope Generator would track the Sustain level
|
|
-- if it was changed to a lower value during the Sustain portion of the
|
|
-- envelope, however, it would not count UP if the Sustain level were set
|
|
-- higher." Instead it would count down to '0'.
|
|
when sustain =>
|
|
divider_value <= 0;
|
|
Dec_rel_sel <='1'; -- select release as input for decay/release table
|
|
if gate = '0' then
|
|
next_state <= releases;
|
|
else
|
|
if (env_counter(7 downto 4) = Sus_Rel(7 downto 4)) then
|
|
next_state <= sustain;
|
|
else
|
|
next_state <= decay;
|
|
end if;
|
|
end if;
|
|
|
|
when releases =>
|
|
divider_rst <= '1';
|
|
exp_table_active <= '1'; -- activate exponential look-up table
|
|
env_cnt_up <= '0'; -- envelope env_counter must count down (decrement)
|
|
divider_value <= divider_dec_rel;
|
|
Dec_rel_sel <= '1'; -- select release as input for decay/release table
|
|
next_state <= release_lp;
|
|
|
|
when release_lp =>
|
|
exp_table_active <= '1'; -- activate exponential look-up table
|
|
env_count_hold_B <= '0'; -- enable envelope env_counter
|
|
env_cnt_up <= '0'; -- envelope env_counter must count down (decrement)
|
|
divider_value <= divider_dec_rel;
|
|
Dec_rel_sel <= '1'; -- select release as input for decay/release table
|
|
if env_counter = "00000000" then
|
|
next_state <= idle;
|
|
else
|
|
if gate = '1' then
|
|
next_state <= idle;
|
|
else
|
|
next_state <= release_lp;
|
|
end if;
|
|
end if;
|
|
|
|
when others =>
|
|
divider_value <= 0;
|
|
Dec_rel_sel <= '0'; -- select decay as input for decay/release table
|
|
next_state <= idle;
|
|
end case;
|
|
end if;
|
|
end process;
|
|
|
|
-- 8 bit up/down env_counter
|
|
Envelope_counter:process(clk_1MHz)
|
|
begin
|
|
if (rising_edge(clk_1MHz)) then
|
|
if ((reset = '1') or (env_cnt_clear = '1')) then
|
|
env_counter <= (others => '0');
|
|
else
|
|
if ((env_count_hold_A = '1') or (env_count_hold_B = '1'))then
|
|
env_counter <= env_counter;
|
|
else
|
|
if (env_cnt_up = '1') then
|
|
env_counter <= env_counter + 1;
|
|
else
|
|
env_counter <= env_counter - 1;
|
|
end if;
|
|
end if;
|
|
end if;
|
|
end if;
|
|
end process;
|
|
|
|
-- Divider :
|
|
-- "A programmable frequency divider was used to set the various rates
|
|
-- (unfortunately I don't remember how many bits the divider was, either 12
|
|
-- or 16 bits). A small look-up table translated the 16 register-programmable
|
|
-- values to the appropriate number to load into the frequency divider.
|
|
-- Depending on what state the Envelope Generator was in (i.e. ADS or R), the
|
|
-- appropriate register would be selected and that number would be translated
|
|
-- and loaded into the divider. Obviously it would have been better to have
|
|
-- individual bit control of the divider which would have provided great
|
|
-- resolution for each rate, however I did not have enough silicon area for a
|
|
-- lot of register bits. Using this approach, I was able to cram a wide range
|
|
-- of rates into 4 bits, allowing the ADSR to be defined in two bytes instead
|
|
-- of eight. The actual numbers in the look-up table were arrived at
|
|
-- subjectively by setting up typical patches on a Sequential Circuits Pro-1
|
|
-- and measuring the envelope times by ear (which is why the available rates
|
|
-- seem strange)!"
|
|
prog_freq_div:process(clk_1MHz)
|
|
begin
|
|
if (rising_edge(clk_1MHz)) then
|
|
if ((reset = '1') or (divider_rst = '1')) then
|
|
env_count_hold_A <= '1';
|
|
divider_counter <= 0;
|
|
else
|
|
if (divider_counter = 0) then
|
|
env_count_hold_A <= '0';
|
|
if (exp_table_active = '1') then
|
|
divider_counter <= exp_table_value;
|
|
else
|
|
divider_counter <= divider_value;
|
|
end if;
|
|
else
|
|
env_count_hold_A <= '1';
|
|
divider_counter <= divider_counter - 1;
|
|
end if;
|
|
end if;
|
|
end if;
|
|
end process;
|
|
|
|
-- Piese-wise linear approximation of an exponential :
|
|
-- "In order to more closely model the exponential decay of sounds, another
|
|
-- look-up table on the output of the Envelope Generator would sequentially
|
|
-- divide the clock to the Envelope Generator by two at specific counts in the
|
|
-- Decay and Release cycles. This created a piece-wise linear approximation of
|
|
-- an exponential. I was particularly happy how well this worked considering
|
|
-- the simplicity of the circuitry. The Attack, however, was linear, but this
|
|
-- sounded fine."
|
|
-- The clock is divided by two at specifiek values of the envelope generator to
|
|
-- create an exponential.
|
|
Exponential_table:process(clk_1MHz)
|
|
BEGIN
|
|
if (rising_edge(clk_1MHz)) then
|
|
if (reset = '1') then
|
|
exp_table_value <= 0;
|
|
else
|
|
case CONV_INTEGER(env_counter) is
|
|
when 0 to 51 => exp_table_value <= divider_value * 16;
|
|
when 52 to 101 => exp_table_value <= divider_value * 8;
|
|
when 102 to 152 => exp_table_value <= divider_value * 4;
|
|
when 153 to 203 => exp_table_value <= divider_value * 2;
|
|
when 204 to 255 => exp_table_value <= divider_value;
|
|
when others => exp_table_value <= divider_value;
|
|
end case;
|
|
end if;
|
|
end if;
|
|
end process;
|
|
|
|
-- Attack Lookup table :
|
|
-- It takes 255 clock cycles from zero to peak value. Therefore the divider
|
|
-- equals (attack rate / clockcycletime of 1MHz clock) / 254;
|
|
Attack_table:process(clk_1MHz)
|
|
begin
|
|
if (rising_edge(clk_1MHz)) then
|
|
if (reset = '1') then
|
|
divider_attack <= 0;
|
|
else
|
|
case Att_dec(7 downto 4) is
|
|
when "0000" => divider_attack <= 8; --attack rate: ( 2mS / 1uS per clockcycle) /254 steps
|
|
when "0001" => divider_attack <= 31; --attack rate: ( 8mS / 1uS per clockcycle) /254 steps
|
|
when "0010" => divider_attack <= 63; --attack rate: ( 16mS / 1uS per clockcycle) /254 steps
|
|
when "0011" => divider_attack <= 94; --attack rate: ( 24mS / 1uS per clockcycle) /254 steps
|
|
when "0100" => divider_attack <= 150; --attack rate: ( 38mS / 1uS per clockcycle) /254 steps
|
|
when "0101" => divider_attack <= 220; --attack rate: ( 56mS / 1uS per clockcycle) /254 steps
|
|
when "0110" => divider_attack <= 268; --attack rate: ( 68mS / 1uS per clockcycle) /254 steps
|
|
when "0111" => divider_attack <= 315; --attack rate: ( 80mS / 1uS per clockcycle) /254 steps
|
|
when "1000" => divider_attack <= 394; --attack rate: ( 100mS / 1uS per clockcycle) /254 steps
|
|
when "1001" => divider_attack <= 984; --attack rate: ( 250mS / 1uS per clockcycle) /254 steps
|
|
when "1010" => divider_attack <= 1968; --attack rate: ( 500mS / 1uS per clockcycle) /254 steps
|
|
when "1011" => divider_attack <= 3150; --attack rate: ( 800mS / 1uS per clockcycle) /254 steps
|
|
when "1100" => divider_attack <= 3937; --attack rate: (1000mS / 1uS per clockcycle) /254 steps
|
|
when "1101" => divider_attack <= 11811; --attack rate: (3000mS / 1uS per clockcycle) /254 steps
|
|
when "1110" => divider_attack <= 19685; --attack rate: (5000mS / 1uS per clockcycle) /254 steps
|
|
when "1111" => divider_attack <= 31496; --attack rate: (8000mS / 1uS per clockcycle) /254 steps
|
|
when others => divider_attack <= 0; --
|
|
end case;
|
|
end if;
|
|
end if;
|
|
end process;
|
|
|
|
Decay_Release_input_select:process(Dec_rel_sel, Att_dec, Sus_Rel)
|
|
begin
|
|
if (Dec_rel_sel = '0') then
|
|
Dec_rel(3 downto 0) <= Att_dec(3 downto 0);
|
|
else
|
|
Dec_rel(3 downto 0) <= Sus_rel(3 downto 0);
|
|
end if;
|
|
end process;
|
|
|
|
-- Decay Lookup table :
|
|
-- It takes 32 * 51 = 1632 clock cycles to fall from peak level to zero.
|
|
-- Release Lookup table :
|
|
-- It takes 32 * 51 = 1632 clock cycles to fall from peak level to zero.
|
|
Decay_Release_table:process(clk_1MHz)
|
|
begin
|
|
if (rising_edge(clk_1MHz)) then
|
|
if reset = '1' then
|
|
divider_dec_rel <= 0;
|
|
else
|
|
case Dec_rel(3 downto 0) is
|
|
when "0000" => divider_dec_rel <= 3; --release rate: ( 6mS / 1uS per clockcycle) / 1632
|
|
when "0001" => divider_dec_rel <= 15; --release rate: ( 24mS / 1uS per clockcycle) / 1632
|
|
when "0010" => divider_dec_rel <= 29; --release rate: ( 48mS / 1uS per clockcycle) / 1632
|
|
when "0011" => divider_dec_rel <= 44; --release rate: ( 72mS / 1uS per clockcycle) / 1632
|
|
when "0100" => divider_dec_rel <= 70; --release rate: ( 114mS / 1uS per clockcycle) / 1632
|
|
when "0101" => divider_dec_rel <= 103; --release rate: ( 168mS / 1uS per clockcycle) / 1632
|
|
when "0110" => divider_dec_rel <= 125; --release rate: ( 204mS / 1uS per clockcycle) / 1632
|
|
when "0111" => divider_dec_rel <= 147; --release rate: ( 240mS / 1uS per clockcycle) / 1632
|
|
when "1000" => divider_dec_rel <= 184; --release rate: ( 300mS / 1uS per clockcycle) / 1632
|
|
when "1001" => divider_dec_rel <= 459; --release rate: ( 750mS / 1uS per clockcycle) / 1632
|
|
when "1010" => divider_dec_rel <= 919; --release rate: ( 1500mS / 1uS per clockcycle) / 1632
|
|
when "1011" => divider_dec_rel <= 1471; --release rate: ( 2400mS / 1uS per clockcycle) / 1632
|
|
when "1100" => divider_dec_rel <= 1838; --release rate: ( 3000mS / 1uS per clockcycle) / 1632
|
|
when "1101" => divider_dec_rel <= 5515; --release rate: ( 9000mS / 1uS per clockcycle) / 1632
|
|
when "1110" => divider_dec_rel <= 9191; --release rate: (15000mS / 1uS per clockcycle) / 1632
|
|
when "1111" => divider_dec_rel <= 14706; --release rate: (24000mS / 1uS per clockcycle) / 1632
|
|
when others => divider_dec_rel <= 0; --
|
|
end case;
|
|
end if;
|
|
end if;
|
|
end process;
|
|
|
|
end Behavioral;
|