LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; USE IEEE.NUMERIC_STD.ALL; -- Entity: LFO_1 (Low Frequency Oscillator) -- Purpose: Applies tremolo effect to audio by modulating amplitude with a triangular wave -- Creates classic audio effects like vibrato, tremolo, and amplitude modulation -- Implements a 3-stage pipeline for efficient real-time audio processing ENTITY LFO_1 IS GENERIC ( CHANNEL_LENGHT : INTEGER := 24; -- Bit width of audio samples (24-bit signed) JOYSTICK_LENGHT : INTEGER := 10; -- Bit width of joystick input (10-bit = 0-1023 range) CLK_PERIOD_NS : INTEGER := 10; -- Clock period in nanoseconds (10ns = 100MHz) TRIANGULAR_COUNTER_LENGHT : INTEGER := 10 -- Bit width of triangular wave counter (affects modulation depth) ); PORT ( -- Clock and Reset aclk : IN STD_LOGIC; -- Main system clock aresetn : IN STD_LOGIC; -- Active-low asynchronous reset -- LFO_1 Control inputs lfo_period : IN STD_LOGIC_VECTOR(JOYSTICK_LENGHT - 1 DOWNTO 0); -- Controls LFO_1 frequency (joystick Y-axis) lfo_enable : IN STD_LOGIC; -- Enable/bypass LFO_1 effect -- Slave AXI Stream interface (audio input) s_axis_tvalid : IN STD_LOGIC; -- Input data valid signal s_axis_tdata : IN STD_LOGIC_VECTOR(CHANNEL_LENGHT - 1 DOWNTO 0); -- Audio sample input s_axis_tlast : IN STD_LOGIC; -- Channel indicator (0=left, 1=right) s_axis_tready : OUT STD_LOGIC; -- Ready to accept input data -- Master AXI Stream interface (audio output) m_axis_tvalid : OUT STD_LOGIC; -- Output data valid signal m_axis_tdata : OUT STD_LOGIC_VECTOR(CHANNEL_LENGHT - 1 DOWNTO 0); -- Modulated audio sample output m_axis_tlast : OUT STD_LOGIC; -- Channel indicator passthrough m_axis_tready : IN STD_LOGIC -- Downstream ready signal ); END ENTITY LFO_1; ARCHITECTURE Behavioral OF LFO_1 IS -- Constants for LFO_1 timing configuration CONSTANT BASE_PERIOD_MICROSECONDS : INTEGER := 1000; -- Base period: 1ms (1kHz base frequency) CONSTANT FREQUENCY_ADJUSTMENT_FACTOR : INTEGER := 90; -- Frequency adjustment sensitivity (clock cycles per joystick unit) CONSTANT JOYSTICK_CENTER_VALUE : INTEGER := 2 ** (JOYSTICK_LENGHT - 1); -- Joystick center position (512 for 10-bit) -- Calculate base clock cycles for 1ms period at current clock frequency CONSTANT BASE_CLOCK_CYCLES : INTEGER := BASE_PERIOD_MICROSECONDS * 1000 / CLK_PERIOD_NS; -- Calculate frequency range limits based on joystick range -- Minimum frequency (fastest LFO_1): occurs when joystick is at minimum position CONSTANT MIN_CLOCK_CYCLES : INTEGER := BASE_CLOCK_CYCLES - FREQUENCY_ADJUSTMENT_FACTOR * (2 ** (JOYSTICK_LENGHT - 1)); -- Maximum frequency (slowest LFO_1): occurs when joystick is at maximum position CONSTANT MAX_CLOCK_CYCLES : INTEGER := BASE_CLOCK_CYCLES + FREQUENCY_ADJUSTMENT_FACTOR * (2 ** (JOYSTICK_LENGHT - 1) - 1); -- Internal signals for LFO_1 control -- Period adjustment based on joystick input (positive = slower, negative = faster) SIGNAL period_adjustment_delta : INTEGER RANGE - 2 ** (JOYSTICK_LENGHT - 1) * FREQUENCY_ADJUSTMENT_FACTOR TO (2 ** (JOYSTICK_LENGHT - 1) - 1) * FREQUENCY_ADJUSTMENT_FACTOR := 0; SIGNAL current_period_cycles : INTEGER RANGE MIN_CLOCK_CYCLES TO MAX_CLOCK_CYCLES := BASE_CLOCK_CYCLES; -- Pipeline stage 1 registers - Input processing and period calculation SIGNAL audio_data_stage1 : STD_LOGIC_VECTOR(CHANNEL_LENGHT - 1 DOWNTO 0) := (OTHERS => '0'); -- Registered audio input SIGNAL enable_flag_stage1 : STD_LOGIC := '0'; -- Registered LFO_1 enable SIGNAL valid_flag_stage1 : STD_LOGIC := '0'; -- Valid data in stage 1 SIGNAL last_flag_stage1 : STD_LOGIC := '0'; -- Registered channel indicator -- Pipeline stage 2 registers - Triangular wave generation SIGNAL triangular_wave_value : unsigned(TRIANGULAR_COUNTER_LENGHT - 1 DOWNTO 0) := (OTHERS => '0'); -- Current triangular wave amplitude SIGNAL wave_direction_up : STD_LOGIC := '1'; -- Triangle wave direction: '1' = ascending, '0' = descending SIGNAL timing_counter : NATURAL RANGE 0 TO MAX_CLOCK_CYCLES := 0; -- Clock cycle counter for LFO_1 timing SIGNAL enable_flag_stage2 : STD_LOGIC := '0'; -- LFO_1 enable flag for stage 2 SIGNAL valid_flag_stage2 : STD_LOGIC := '0'; -- Valid data in stage 2 SIGNAL last_flag_stage2 : STD_LOGIC := '0'; -- Channel indicator for stage 2 SIGNAL audio_data_stage2 : STD_LOGIC_VECTOR(CHANNEL_LENGHT - 1 DOWNTO 0) := (OTHERS => '0'); -- Audio data for stage 2 -- Pipeline stage 3 registers - Modulation and output -- Extended width to accommodate multiplication result before scaling SIGNAL multiplication_result : STD_LOGIC_VECTOR(CHANNEL_LENGHT + TRIANGULAR_COUNTER_LENGHT - 1 DOWNTO 0) := (OTHERS => '0'); -- Internal AXI4-Stream control signals SIGNAL master_valid_internal : STD_LOGIC := '0'; -- Internal output valid signal SIGNAL slave_ready_internal : STD_LOGIC := '1'; -- Internal input ready signal BEGIN -- Direct connection: tlast passes through unchanged (maintains channel timing) m_axis_tlast <= last_flag_stage1; -- Pipeline stage 1: Input registration and LFO_1 period calculation -- This stage captures input data and calculates the LFO_1 period based on joystick position input_processing_stage : PROCESS (aclk) BEGIN IF rising_edge(aclk) THEN IF aresetn = '0' THEN -- Reset all stage 1 registers to safe initial states audio_data_stage1 <= (OTHERS => '0'); -- Clear audio data current_period_cycles <= BASE_CLOCK_CYCLES; -- Set to base frequency enable_flag_stage1 <= '0'; -- Disable LFO_1 valid_flag_stage1 <= '0'; -- No valid data last_flag_stage1 <= '0'; -- Clear channel indicator ELSE -- Calculate LFO_1 period based on joystick y-axis input -- Joystick mapping: -- 0-511: Faster than base frequency (shorter period) -- 512: Base frequency (1kHz) -- 513-1023: Slower than base frequency (longer period) period_adjustment_delta <= (to_integer(unsigned(lfo_period)) - JOYSTICK_CENTER_VALUE) * FREQUENCY_ADJUSTMENT_FACTOR; current_period_cycles <= BASE_CLOCK_CYCLES - period_adjustment_delta; -- AXI4-Stream handshake: accept new data when both valid and ready IF s_axis_tvalid = '1' AND slave_ready_internal = '1' THEN audio_data_stage1 <= s_axis_tdata; -- Register input audio sample enable_flag_stage1 <= lfo_enable; -- Register enable control valid_flag_stage1 <= '1'; -- Mark data as valid for next stage last_flag_stage1 <= s_axis_tlast; -- Register channel boundary signal ELSE valid_flag_stage1 <= '0'; -- No valid data to pass to next stage END IF; END IF; END IF; END PROCESS input_processing_stage; -- Pipeline stage 2: Triangular wave generation -- This stage generates the triangular wave that will modulate the audio amplitude triangular_wave_generator : PROCESS (aclk) BEGIN IF rising_edge(aclk) THEN IF aresetn = '0' THEN -- Reset triangular wave generator to initial state timing_counter <= 0; -- Clear timing counter triangular_wave_value <= (OTHERS => '0'); -- Start at zero amplitude wave_direction_up <= '1'; -- Start counting up enable_flag_stage2 <= '0'; -- Disable LFO_1 valid_flag_stage2 <= '0'; -- No valid data last_flag_stage2 <= '0'; -- Clear channel indicator audio_data_stage2 <= (OTHERS => '0'); -- Clear audio data ELSE -- Pass through pipeline registers from stage 1 to stage 2 enable_flag_stage2 <= enable_flag_stage1; -- Forward enable flag valid_flag_stage2 <= valid_flag_stage1; -- Forward valid flag last_flag_stage2 <= last_flag_stage1; -- Forward channel indicator audio_data_stage2 <= audio_data_stage1; -- Forward audio data -- Generate triangular wave when LFO_1 is enabled IF enable_flag_stage1 = '1' THEN -- Clock divider: update triangular counter based on calculated period IF timing_counter < current_period_cycles THEN timing_counter <= timing_counter + 1; -- Count towards period target ELSE timing_counter <= 0; -- Reset counter for next period -- Update triangular wave: count up or down based on current direction -- This creates the classic triangular waveform shape IF wave_direction_up = '1' THEN -- Ascending phase: check if we reached maximum amplitude IF triangular_wave_value = (2 ** TRIANGULAR_COUNTER_LENGHT) - 1 THEN wave_direction_up <= '0'; -- Switch to descending phase triangular_wave_value <= triangular_wave_value - 1; -- Start decreasing ELSE triangular_wave_value <= triangular_wave_value + 1; -- Continue increasing END IF; ELSE -- Descending phase: check if we reached minimum amplitude IF triangular_wave_value = 0 THEN wave_direction_up <= '1'; -- Switch to ascending phase triangular_wave_value <= triangular_wave_value + 1; -- Start increasing ELSE triangular_wave_value <= triangular_wave_value - 1; -- Continue decreasing END IF; END IF; END IF; ELSE -- LFO_1 disabled: reset triangular wave generator to idle state timing_counter <= 0; -- Clear timing counter triangular_wave_value <= (OTHERS => '0'); -- Reset to zero amplitude wave_direction_up <= '1'; -- Reset to ascending direction END IF; END IF; END IF; END PROCESS triangular_wave_generator; -- Pipeline stage 3: Audio modulation and output control -- This stage applies the LFO_1 effect by multiplying audio samples with the triangular wave modulation_and_output : PROCESS (aclk) BEGIN IF rising_edge(aclk) THEN IF aresetn = '0' THEN -- Reset output stage to safe initial state m_axis_tdata <= (OTHERS => '0'); -- Clear output data master_valid_internal <= '0'; -- No valid output slave_ready_internal <= '1'; -- Ready to accept input ELSE -- Output flow control: handle backpressure from downstream modules IF master_valid_internal = '1' AND m_axis_tready = '0' THEN -- Downstream not ready: maintain current output valid state -- This implements proper AXI4-Stream backpressure handling master_valid_internal <= '1'; ELSIF valid_flag_stage2 = '1' THEN -- New data available from stage 2: apply LFO_1 effect or bypass IF enable_flag_stage2 = '1' THEN -- Apply LFO_1 tremolo effect: multiply audio sample by triangular wave -- This creates amplitude modulation (tremolo effect) multiplication_result <= STD_LOGIC_VECTOR( resize( signed(audio_data_stage2) * signed('0' & triangular_wave_value), multiplication_result'length ) ); -- Scale down result by removing lower bits (equivalent to division by 2^TRIANGULAR_COUNTER_LENGHT) -- This maintains proper audio amplitude range after multiplication m_axis_tdata <= multiplication_result(multiplication_result'high DOWNTO TRIANGULAR_COUNTER_LENGHT); ELSE -- LFO_1 disabled: pass audio through unchanged (bypass mode) -- This allows seamless switching between effect and clean audio m_axis_tdata <= audio_data_stage2; END IF; master_valid_internal <= '1'; -- Mark output as valid ELSE -- No new data available: clear output valid flag master_valid_internal <= '0'; END IF; -- AXI4-Stream ready signal management for proper flow control IF master_valid_internal = '1' AND m_axis_tready = '1' THEN -- Successful output handshake: ready for new input data slave_ready_internal <= '1'; ELSIF s_axis_tvalid = '1' AND slave_ready_internal = '1' THEN -- Accepted new input: not ready until current output is consumed -- This prevents data loss in the pipeline slave_ready_internal <= '0'; END IF; END IF; END IF; END PROCESS modulation_and_output; -- Output signal assignments s_axis_tready <= slave_ready_internal; -- Connect internal ready to output port m_axis_tvalid <= master_valid_internal; -- Connect internal valid to output port -- LFO_1 Effect Summary: -- 1. Stage 1: Calculates LFO_1 frequency based on joystick position -- 2. Stage 2: Generates triangular wave at calculated frequency -- 3. Stage 3: Multiplies audio samples by triangular wave (tremolo effect) -- -- Audio Effect Characteristics: -- - Tremolo: Periodic amplitude modulation creates "shaking" sound -- - Frequency range: Approximately 0.1Hz to 10Hz (typical for audio LFO_1) -- - Modulation depth: Controlled by TRIANGULAR_COUNTER_LENGHT generic -- - Bypass capability: Clean audio passthrough when disabled -- -- Pipeline Benefits: -- - Maintains real-time audio processing with no dropouts -- - Allows complex calculations without affecting audio timing -- - Provides proper AXI4-Stream flow control and backpressure handling END ARCHITECTURE Behavioral;