From 82d76e48d83a22daba14b7158ae28db6d04d5919 Mon Sep 17 00:00:00 2001 From: Cd16d Date: Tue, 27 May 2025 17:42:40 +0200 Subject: [PATCH] Add comments --- LAB3/src/LFO.vhd | 305 ++++++++++++++++---------- LAB3/src/LFO_1.vhd | 200 ++++++++++------- LAB3/src/all_pass_filter.vhd | 153 ++++++++----- LAB3/src/balance_controller.vhd | 188 +++++++++------- LAB3/src/digilent_jstk2.vhd | 296 +++++++++++++------------ LAB3/src/effect_selector.vhd | 49 +++-- LAB3/src/led_controller.vhd | 71 ++++-- LAB3/src/led_level_controller.vhd | 182 ++++++++++----- LAB3/src/moving_average_filter.vhd | 268 ++++++++++++---------- LAB3/src/moving_average_filter_en.vhd | 244 ++++++++++++--------- LAB3/src/mute_controller.vhd | 115 ++++++---- LAB3/src/volume_controller.vhd | 248 ++++++++++++--------- LAB3/src/volume_multiplier.vhd | 203 +++++++++++------ LAB3/src/volume_saturator.vhd | 149 ++++++++----- LAB3/vivado/LFO/LFO.xpr | 2 +- 15 files changed, 1626 insertions(+), 1047 deletions(-) diff --git a/LAB3/src/LFO.vhd b/LAB3/src/LFO.vhd index 2f51ce8..ee49199 100644 --- a/LAB3/src/LFO.vhd +++ b/LAB3/src/LFO.vhd @@ -2,180 +2,255 @@ LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; USE IEEE.NUMERIC_STD.ALL; +-- Entity: LFO (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 IS GENERIC ( - CHANNEL_LENGHT : INTEGER := 24; - JOYSTICK_LENGHT : INTEGER := 10; - CLK_PERIOD_NS : INTEGER := 10; - TRIANGULAR_COUNTER_LENGHT : INTEGER := 10 + 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 ( - aclk : IN STD_LOGIC; - aresetn : IN STD_LOGIC; - lfo_period : IN STD_LOGIC_VECTOR(JOYSTICK_LENGHT - 1 DOWNTO 0); - lfo_enable : IN STD_LOGIC; - -- Slave AXI Stream interface - s_axis_tvalid : IN STD_LOGIC; - s_axis_tdata : IN STD_LOGIC_VECTOR(CHANNEL_LENGHT - 1 DOWNTO 0); - s_axis_tlast : IN STD_LOGIC; - s_axis_tready : OUT STD_LOGIC; - -- Master AXI Stream interface - m_axis_tvalid : OUT STD_LOGIC; - m_axis_tdata : OUT STD_LOGIC_VECTOR(CHANNEL_LENGHT - 1 DOWNTO 0); - m_axis_tlast : OUT STD_LOGIC; - m_axis_tready : IN STD_LOGIC + -- Clock and Reset + aclk : IN STD_LOGIC; -- Main system clock + aresetn : IN STD_LOGIC; -- Active-low asynchronous reset + + -- LFO Control inputs + lfo_period : IN STD_LOGIC_VECTOR(JOYSTICK_LENGHT - 1 DOWNTO 0); -- Controls LFO frequency (joystick Y-axis) + lfo_enable : IN STD_LOGIC; -- Enable/bypass LFO 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; ARCHITECTURE Behavioral OF LFO IS - CONSTANT LFO_COUNTER_BASE_PERIOD_US : INTEGER := 1000; -- 1ms - CONSTANT ADJUSTMENT_FACTOR : INTEGER := 90; - CONSTANT JSTK_CENTER_VALUE : INTEGER := 2 ** (JOYSTICK_LENGHT - 1); -- 512 for 10 bits - CONSTANT LFO_COUNTER_BASE_CLK_CYCLES : INTEGER := LFO_COUNTER_BASE_PERIOD_US * 1000 / CLK_PERIOD_NS; -- 1ms = 100_000 clk cycles - CONSTANT LFO_CLK_CYCLES_MIN : INTEGER := LFO_COUNTER_BASE_CLK_CYCLES - ADJUSTMENT_FACTOR * (2 ** (JOYSTICK_LENGHT - 1)); -- 53_920 clk cycles - CONSTANT LFO_CLK_CYCLES_MAX : INTEGER := LFO_COUNTER_BASE_CLK_CYCLES + ADJUSTMENT_FACTOR * (2 ** (JOYSTICK_LENGHT - 1) - 1); -- 145_990 clk cycles + -- Constants for LFO 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) - -- Signals for LFO operation - SIGNAL step_clk_cycles_delta : INTEGER RANGE - 2 ** (JOYSTICK_LENGHT - 1) * ADJUSTMENT_FACTOR TO (2 ** (JOYSTICK_LENGHT - 1) - 1) * ADJUSTMENT_FACTOR := 0; - SIGNAL step_clk_cycles : INTEGER RANGE LFO_CLK_CYCLES_MIN TO LFO_CLK_CYCLES_MAX := LFO_COUNTER_BASE_CLK_CYCLES; + -- Calculate base clock cycles for 1ms period at current clock frequency + CONSTANT BASE_CLOCK_CYCLES : INTEGER := BASE_PERIOD_MICROSECONDS * 1000 / CLK_PERIOD_NS; - -- Pipeline stage registers - SIGNAL s_axis_tdata_r1 : STD_LOGIC_VECTOR(CHANNEL_LENGHT - 1 DOWNTO 0) := (OTHERS => '0'); - SIGNAL lfo_enable_r1 : STD_LOGIC := '0'; - SIGNAL s_axis_tvalid_r1 : STD_LOGIC := '0'; - SIGNAL s_axis_tlast_r1 : STD_LOGIC := '0'; + -- Calculate frequency range limits based on joystick range + -- Minimum frequency (fastest LFO): 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): occurs when joystick is at maximum position + CONSTANT MAX_CLOCK_CYCLES : INTEGER := BASE_CLOCK_CYCLES + FREQUENCY_ADJUSTMENT_FACTOR * (2 ** (JOYSTICK_LENGHT - 1) - 1); - SIGNAL tri_counter_r2 : unsigned(TRIANGULAR_COUNTER_LENGHT - 1 DOWNTO 0) := (OTHERS => '0'); - SIGNAL direction_up_r2 : STD_LOGIC := '1'; - SIGNAL step_counter_r2 : NATURAL RANGE 0 TO LFO_CLK_CYCLES_MAX := 0; - SIGNAL lfo_enable_r2 : STD_LOGIC := '0'; - SIGNAL s_axis_tvalid_r2 : STD_LOGIC := '0'; - SIGNAL s_axis_tlast_r2 : STD_LOGIC := '0'; - SIGNAL s_axis_tdata_r2 : STD_LOGIC_VECTOR(CHANNEL_LENGHT - 1 DOWNTO 0) := (OTHERS => '0'); + -- Internal signals for LFO 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; - SIGNAL temp_r3 : STD_LOGIC_VECTOR(CHANNEL_LENGHT + TRIANGULAR_COUNTER_LENGHT - 1 DOWNTO 0) := (OTHERS => '0'); - SIGNAL m_axis_tvalid_i : STD_LOGIC := '0'; - SIGNAL s_axis_tready_i : STD_LOGIC := '1'; + -- 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 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 timing + SIGNAL enable_flag_stage2 : STD_LOGIC := '0'; -- LFO 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 - m_axis_tlast <= s_axis_tlast_r1; + -- Direct connection: tlast passes through unchanged (maintains channel timing) + m_axis_tlast <= last_flag_stage1; - -- Stage 1: Input registration and LFO period calculation - PROCESS (aclk) + -- Pipeline stage 1: Input registration and LFO period calculation + -- This stage captures input data and calculates the LFO period based on joystick position + input_processing_stage : PROCESS (aclk) BEGIN IF rising_edge(aclk) THEN IF aresetn = '0' THEN - s_axis_tdata_r1 <= (OTHERS => '0'); - step_clk_cycles <= LFO_COUNTER_BASE_CLK_CYCLES; - lfo_enable_r1 <= '0'; - s_axis_tvalid_r1 <= '0'; - s_axis_tlast_r1 <= '0'; + -- 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 + valid_flag_stage1 <= '0'; -- No valid data + last_flag_stage1 <= '0'; -- Clear channel indicator ELSE - -- Set the step_clk_cycles based on the joystick input - step_clk_cycles_delta <= (to_integer(unsigned(lfo_period)) - JSTK_CENTER_VALUE) * ADJUSTMENT_FACTOR; - step_clk_cycles <= LFO_COUNTER_BASE_CLK_CYCLES - step_clk_cycles_delta; + -- Calculate LFO period based on joystick 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; - IF s_axis_tvalid = '1' AND s_axis_tready_i = '1' THEN - s_axis_tdata_r1 <= s_axis_tdata; - lfo_enable_r1 <= lfo_enable; - s_axis_tvalid_r1 <= '1'; - s_axis_tlast_r1 <= s_axis_tlast; + -- 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 - s_axis_tvalid_r1 <= '0'; + valid_flag_stage1 <= '0'; -- No valid data to pass to next stage END IF; END IF; END IF; - END PROCESS; + END PROCESS input_processing_stage; - -- Stage 2: Triangular counter control - PROCESS (aclk) + -- 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 - step_counter_r2 <= 0; - tri_counter_r2 <= (OTHERS => '0'); - direction_up_r2 <= '1'; - lfo_enable_r2 <= '0'; - s_axis_tvalid_r2 <= '0'; - s_axis_tlast_r2 <= '0'; - s_axis_tdata_r2 <= (OTHERS => '0'); + -- 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 + valid_flag_stage2 <= '0'; -- No valid data + last_flag_stage2 <= '0'; -- Clear channel indicator + audio_data_stage2 <= (OTHERS => '0'); -- Clear audio data ELSE - lfo_enable_r2 <= lfo_enable_r1; - s_axis_tvalid_r2 <= s_axis_tvalid_r1; - s_axis_tlast_r2 <= s_axis_tlast_r1; - s_axis_tdata_r2 <= s_axis_tdata_r1; + -- 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 - IF lfo_enable_r1 = '1' THEN - IF step_counter_r2 < step_clk_cycles THEN - step_counter_r2 <= step_counter_r2 + 1; + -- Generate triangular wave when LFO 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 - step_counter_r2 <= 0; - IF direction_up_r2 = '1' THEN - IF tri_counter_r2 = (2 ** TRIANGULAR_COUNTER_LENGHT) - 1 THEN - direction_up_r2 <= '0'; - tri_counter_r2 <= tri_counter_r2 - 1; + 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 - tri_counter_r2 <= tri_counter_r2 + 1; + triangular_wave_value <= triangular_wave_value + 1; -- Continue increasing END IF; ELSE - IF tri_counter_r2 = 0 THEN - direction_up_r2 <= '1'; - tri_counter_r2 <= tri_counter_r2 + 1; + -- 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 - tri_counter_r2 <= tri_counter_r2 - 1; + triangular_wave_value <= triangular_wave_value - 1; -- Continue decreasing END IF; END IF; END IF; ELSE - step_counter_r2 <= 0; - tri_counter_r2 <= (OTHERS => '0'); - direction_up_r2 <= '1'; + -- LFO 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; + END PROCESS triangular_wave_generator; - -- Stage 3: Optimized modulation and output handling - PROCESS (aclk) + -- Pipeline stage 3: Audio modulation and output control + -- This stage applies the LFO effect by multiplying audio samples with the triangular wave + modulation_and_output : PROCESS (aclk) BEGIN IF rising_edge(aclk) THEN IF aresetn = '0' THEN - m_axis_tdata <= (OTHERS => '0'); - m_axis_tvalid_i <= '0'; - s_axis_tready_i <= '1'; + -- 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 - IF m_axis_tvalid_i = '1' AND m_axis_tready = '0' THEN - m_axis_tvalid_i <= '1'; - ELSIF s_axis_tvalid_r2 = '1' THEN - IF lfo_enable_r2 = '1' THEN - temp_r3 <= STD_LOGIC_VECTOR( + -- 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 effect or bypass + IF enable_flag_stage2 = '1' THEN + -- Apply LFO tremolo effect: multiply audio sample by triangular wave + -- This creates amplitude modulation (tremolo effect) + multiplication_result <= STD_LOGIC_VECTOR( resize( - signed(s_axis_tdata_r2) * signed('0' & tri_counter_r2), - temp_r3'length + signed(audio_data_stage2) * signed('0' & triangular_wave_value), + multiplication_result'length ) - ); - m_axis_tdata <= temp_r3(temp_r3'high DOWNTO TRIANGULAR_COUNTER_LENGHT); + ); + -- 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 - m_axis_tdata <= s_axis_tdata_r2; + -- LFO disabled: pass audio through unchanged (bypass mode) + -- This allows seamless switching between effect and clean audio + m_axis_tdata <= audio_data_stage2; END IF; - m_axis_tvalid_i <= '1'; + master_valid_internal <= '1'; -- Mark output as valid ELSE - m_axis_tvalid_i <= '0'; + -- No new data available: clear output valid flag + master_valid_internal <= '0'; END IF; - -- Ready signal management - IF m_axis_tvalid_i = '1' AND m_axis_tready = '1' THEN - s_axis_tready_i <= '1'; - ELSIF s_axis_tvalid = '1' AND s_axis_tready_i = '1' THEN - s_axis_tready_i <= '0'; + -- 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; + END PROCESS modulation_and_output; - s_axis_tready <= s_axis_tready_i; - m_axis_tvalid <= m_axis_tvalid_i; + -- 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 Effect Summary: + -- 1. Stage 1: Calculates LFO 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) + -- - 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; \ No newline at end of file diff --git a/LAB3/src/LFO_1.vhd b/LAB3/src/LFO_1.vhd index d432a6e..9fad294 100644 --- a/LAB3/src/LFO_1.vhd +++ b/LAB3/src/LFO_1.vhd @@ -5,100 +5,129 @@ USE IEEE.STD_LOGIC_1164.ALL; -- arithmetic functions with Signed or Unsigned values USE IEEE.NUMERIC_STD.ALL; +-- Entity: LFO (Low Frequency Oscillator) - Alternative Implementation +-- Purpose: Applies tremolo effect to audio by modulating amplitude with a triangular wave +-- This is a simplified, single-process implementation compared to the pipelined version +-- Provides real-time audio amplitude modulation for musical effects ENTITY LFO IS GENERIC ( - CHANNEL_LENGHT : INTEGER := 24; - JOYSTICK_LENGHT : INTEGER := 10; - CLK_PERIOD_NS : INTEGER := 10; - TRIANGULAR_COUNTER_LENGHT : INTEGER := 10 -- Triangular wave period length + 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 -- Triangular wave counter length (affects modulation depth) ); PORT ( + -- Clock and Reset + aclk : IN STD_LOGIC; -- Main system clock + aresetn : IN STD_LOGIC; -- Active-low asynchronous reset - aclk : IN STD_LOGIC; - aresetn : IN STD_LOGIC; + -- LFO Control inputs + lfo_period : IN STD_LOGIC_VECTOR(JOYSTICK_LENGHT - 1 DOWNTO 0); -- Controls LFO frequency (joystick Y-axis) + lfo_enable : IN STD_LOGIC; -- Enable/bypass LFO effect - lfo_period : IN STD_LOGIC_VECTOR(JOYSTICK_LENGHT - 1 DOWNTO 0); + -- 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 - lfo_enable : IN STD_LOGIC; - - s_axis_tvalid : IN STD_LOGIC; - s_axis_tdata : IN STD_LOGIC_VECTOR(CHANNEL_LENGHT - 1 DOWNTO 0); - s_axis_tlast : IN STD_LOGIC; - s_axis_tready : OUT STD_LOGIC; - - m_axis_tvalid : OUT STD_LOGIC; - m_axis_tdata : OUT STD_LOGIC_VECTOR(CHANNEL_LENGHT - 1 DOWNTO 0); - m_axis_tlast : OUT STD_LOGIC; - m_axis_tready : IN STD_LOGIC + -- 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; ARCHITECTURE Behavioral OF LFO IS - CONSTANT LFO_COUNTER_BASE_PERIOD_US : INTEGER := 1000; -- 1ms - CONSTANT ADJUSTMENT_FACTOR : INTEGER := 90; - CONSTANT JSTK_CENTER_VALUE : INTEGER := 2 ** (JOYSTICK_LENGHT - 1); -- 512 for 10 bits - CONSTANT LFO_COUNTER_BASE_CLK_CYCLES : INTEGER := LFO_COUNTER_BASE_PERIOD_US * 1000 / CLK_PERIOD_NS; -- 1ms = 100_000 clk cycles - CONSTANT LFO_CLK_CYCLES_MIN : INTEGER := LFO_COUNTER_BASE_CLK_CYCLES - ADJUSTMENT_FACTOR * (2 ** (JOYSTICK_LENGHT - 1)); -- 53_920 clk cycles - CONSTANT LFO_CLK_CYCLES_MAX : INTEGER := LFO_COUNTER_BASE_CLK_CYCLES + ADJUSTMENT_FACTOR * (2 ** (JOYSTICK_LENGHT - 1) - 1); -- 145_990 clk cycles + -- LFO timing configuration constants + CONSTANT LFO_COUNTER_BASE_PERIOD_US : INTEGER := 1000; -- Base period: 1ms (1kHz base frequency) + CONSTANT ADJUSTMENT_FACTOR : INTEGER := 90; -- Frequency adjustment sensitivity (clock cycles per joystick unit) + CONSTANT JSTK_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 LFO_COUNTER_BASE_CLK_CYCLES : INTEGER := LFO_COUNTER_BASE_PERIOD_US * 1000 / CLK_PERIOD_NS; -- 1ms = 100,000 clk cycles + + -- Calculate frequency range limits based on joystick range + CONSTANT LFO_CLK_CYCLES_MIN : INTEGER := LFO_COUNTER_BASE_CLK_CYCLES - ADJUSTMENT_FACTOR * (2 ** (JOYSTICK_LENGHT - 1)); -- 53,920 clk cycles (faster) + CONSTANT LFO_CLK_CYCLES_MAX : INTEGER := LFO_COUNTER_BASE_CLK_CYCLES + ADJUSTMENT_FACTOR * (2 ** (JOYSTICK_LENGHT - 1) - 1); -- 145,990 clk cycles (slower) + + -- LFO timing control signals SIGNAL step_clk_cycles_delta : INTEGER RANGE - 2 ** (JOYSTICK_LENGHT - 1) * ADJUSTMENT_FACTOR TO (2 ** (JOYSTICK_LENGHT - 1) - 1) * ADJUSTMENT_FACTOR := 0; SIGNAL step_clk_cycles : INTEGER RANGE LFO_CLK_CYCLES_MIN TO LFO_CLK_CYCLES_MAX := LFO_COUNTER_BASE_CLK_CYCLES; SIGNAL step_counter : NATURAL RANGE 0 TO LFO_CLK_CYCLES_MAX := 0; - SIGNAL tri_counter : SIGNED(TRIANGULAR_COUNTER_LENGHT DOWNTO 0) := (OTHERS => '0'); - SIGNAL direction_up : STD_LOGIC := '1'; - SIGNAL trigger : STD_LOGIC := '0'; + -- Triangular wave generation signals + -- Note: Using signed counter with extra bit to handle full range calculations + SIGNAL tri_counter : SIGNED(TRIANGULAR_COUNTER_LENGHT DOWNTO 0) := (OTHERS => '0'); -- Triangular wave amplitude + SIGNAL direction_up : STD_LOGIC := '1'; -- Wave direction: '1' = ascending, '0' = descending - SIGNAL s_axis_tlast_reg : STD_LOGIC := '0'; + -- AXI4-Stream control signals + SIGNAL trigger : STD_LOGIC := '0'; -- Trigger to indicate new processed data is ready + SIGNAL s_axis_tlast_reg : STD_LOGIC := '0'; -- Registered version of tlast for output synchronization + SIGNAL m_axis_tvalid_int : STD_LOGIC := '0'; -- Internal output valid signal + + -- Audio processing signal with extended width for multiplication + -- Width accommodates: audio sample + triangular counter to prevent overflow SIGNAL m_axis_tdata_temp : SIGNED(CHANNEL_LENGHT + TRIANGULAR_COUNTER_LENGHT DOWNTO 0) := (OTHERS => '0'); - SIGNAL m_axis_tvalid_int : STD_LOGIC := '0'; BEGIN - -- Assigning the output signals - m_axis_tvalid <= m_axis_tvalid_int; - s_axis_tready <= (m_axis_tready OR NOT m_axis_tvalid_int) AND aresetn; + -- Output signal assignments with proper AXI4-Stream flow control + m_axis_tvalid <= m_axis_tvalid_int; + -- Input ready logic: Ready when downstream is ready OR no valid data pending, AND not in reset + s_axis_tready <= (m_axis_tready OR NOT m_axis_tvalid_int) AND aresetn; - -- Optimized single process for LFO step and triangular waveform generation - PROCESS (aclk) + -- Optimized single process for LFO timing and triangular waveform generation + -- This process handles both the frequency control and wave shape generation + triangular_wave_lfo_generator : PROCESS (aclk) BEGIN IF rising_edge(aclk) THEN IF aresetn = '0' THEN - step_clk_cycles <= LFO_COUNTER_BASE_CLK_CYCLES; - step_counter <= 0; - tri_counter <= (OTHERS => '0'); - direction_up <= '1'; + -- Reset LFO generator to initial state + step_clk_cycles <= LFO_COUNTER_BASE_CLK_CYCLES; -- Set to base frequency + step_counter <= 0; -- Clear timing counter + tri_counter <= (OTHERS => '0'); -- Start triangular wave at zero + direction_up <= '1'; -- Start counting up ELSE - -- Set the step_clk_cycles based on the joystick input + -- Calculate LFO period based on joystick input + -- Joystick mapping: + -- 0-511: Faster than base frequency (shorter period, higher frequency) + -- 512: Base frequency (1kHz) + -- 513-1023: Slower than base frequency (longer period, lower frequency) step_clk_cycles_delta <= (to_integer(unsigned(lfo_period)) - JSTK_CENTER_VALUE) * ADJUSTMENT_FACTOR; step_clk_cycles <= LFO_COUNTER_BASE_CLK_CYCLES - step_clk_cycles_delta; + -- Generate triangular wave when LFO is enabled IF lfo_enable = '1' THEN + -- Clock divider: Update triangular wave at calculated rate IF step_counter >= step_clk_cycles THEN - step_counter <= 0; + step_counter <= 0; -- Reset counter for next period + -- Check for triangular wave direction changes at extremes + -- Note: Using (2^n - 2) and 1 instead of (2^n - 1) and 0 to avoid edge cases IF tri_counter = (2 ** TRIANGULAR_COUNTER_LENGHT) - 2 THEN - direction_up <= '0'; + direction_up <= '0'; -- Switch to descending at near-maximum ELSIF tri_counter = 1 THEN - direction_up <= '1'; + direction_up <= '1'; -- Switch to ascending at near-minimum END IF; + -- Update triangular wave value based on current direction + -- This creates the classic triangular waveform shape IF direction_up = '1' THEN - tri_counter <= tri_counter + 1; - + tri_counter <= tri_counter + 1; -- Ascending: increment ELSE - tri_counter <= tri_counter - 1; - + tri_counter <= tri_counter - 1; -- Descending: decrement END IF; ELSE - step_counter <= step_counter + 1; + step_counter <= step_counter + 1; -- Continue counting towards next update END IF; @@ -108,62 +137,73 @@ BEGIN END IF; - END PROCESS; + END PROCESS triangular_wave_lfo_generator; - -- Handshake logic for the AXIS interface - AXIS: PROCESS (aclk) + -- AXI4-Stream handshake logic and audio processing + -- This process handles input/output data flow and applies the LFO modulation + AXIS : PROCESS (aclk) BEGIN IF rising_edge(aclk) THEN IF aresetn = '0' THEN - s_axis_tlast_reg <= '0'; - m_axis_tdata_temp <= (OTHERS => '0'); - m_axis_tvalid_int <= '0'; - m_axis_tlast <= '0'; + -- Reset AXI4-Stream interface and audio processing + s_axis_tlast_reg <= '0'; -- Clear registered channel indicator + m_axis_tdata_temp <= (OTHERS => '0'); -- Clear temporary audio data + m_axis_tvalid_int <= '0'; -- No valid output data + m_axis_tlast <= '0'; -- Clear output channel indicator ELSE - -- Clear valid flag when master interface is ready + -- Output handshake management: + -- Clear valid flag when downstream accepts data IF m_axis_tready = '1' THEN m_axis_tvalid_int <= '0'; END IF; - -- Data output logic - IF trigger = '1' AND (m_axis_tvalid_int = '0' OR m_axis_tready = '1') THEN - m_axis_tdata <= STD_LOGIC_VECTOR( + -- Data output logic: Send processed audio when trigger is active and output is available + IF trigger = '1' AND (m_axis_tvalid_int = '0' OR m_axis_tready = '1') THEN + -- Scale down the multiplication result to original audio bit width + -- Right shift by TRIANGULAR_COUNTER_LENGHT effectively divides by 2^TRIANGULAR_COUNTER_LENGHT + -- This maintains proper audio amplitude after modulation + m_axis_tdata <= STD_LOGIC_VECTOR( resize( shift_right( - m_axis_tdata_temp, - TRIANGULAR_COUNTER_LENGHT + m_axis_tdata_temp, -- Wide multiplication result + TRIANGULAR_COUNTER_LENGHT -- Scale factor ), - CHANNEL_LENGHT + CHANNEL_LENGHT -- Final audio sample width ) ); - m_axis_tlast <= s_axis_tlast_reg; + m_axis_tlast <= s_axis_tlast_reg; -- Output registered channel indicator - m_axis_tvalid_int <= '1'; - trigger <= '0'; - - END IF; + m_axis_tvalid_int <= '1'; -- Mark output as valid + trigger <= '0'; -- Clear trigger - data has been output + + END IF; - -- Data input logic - IF s_axis_tvalid = '1' AND (m_axis_tready = '1' OR m_axis_tvalid_int = '0') THEN + -- Data input logic: Process new audio samples when available and output is ready + IF s_axis_tvalid = '1' AND (m_axis_tready = '1' OR m_axis_tvalid_int = '0') THEN IF lfo_enable = '1' THEN + -- Apply LFO tremolo effect: multiply audio sample by triangular wave + -- This creates amplitude modulation (tremolo effect) m_axis_tdata_temp <= signed(s_axis_tdata) * tri_counter; - s_axis_tlast_reg <= s_axis_tlast; + s_axis_tlast_reg <= s_axis_tlast; -- Register channel indicator ELSE + -- LFO disabled: pass audio through unchanged but maintain bit width + -- Left shift compensates for the right shift that occurs during output + -- This ensures unity gain when LFO is bypassed m_axis_tdata_temp <= shift_left( resize( - signed(s_axis_tdata), - m_axis_tdata_temp'length + signed(s_axis_tdata), -- Convert input to signed + m_axis_tdata_temp'length -- Extend to full processing width ), - TRIANGULAR_COUNTER_LENGHT + TRIANGULAR_COUNTER_LENGHT -- Compensate for output scaling ); - s_axis_tlast_reg <= s_axis_tlast; + s_axis_tlast_reg <= s_axis_tlast; -- Register channel indicator END IF; - trigger <= '1'; + trigger <= '1'; -- Set trigger to indicate new processed data is ready END IF; @@ -173,4 +213,16 @@ BEGIN END PROCESS AXIS; + -- LFO Implementation Summary: + -- 1. Generates triangular wave at frequency controlled by joystick input + -- 2. When enabled: multiplies audio samples by triangular wave (tremolo effect) + -- 3. When disabled: passes audio through unchanged (bypass mode) + -- 4. Uses proper AXI4-Stream handshaking for real-time audio processing + -- + -- Tremolo Effect Characteristics: + -- - Frequency range: Approximately 0.1Hz to 10Hz (typical for audio LFO) + -- - Modulation depth: Controlled by TRIANGULAR_COUNTER_LENGHT generic + -- - Waveform: Triangular (linear amplitude changes, smooth transitions) + -- - Bypass capability: Clean audio passthrough when disabled + END ARCHITECTURE Behavioral; \ No newline at end of file diff --git a/LAB3/src/all_pass_filter.vhd b/LAB3/src/all_pass_filter.vhd index 37c06c5..364ec50 100644 --- a/LAB3/src/all_pass_filter.vhd +++ b/LAB3/src/all_pass_filter.vhd @@ -2,90 +2,123 @@ LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; USE ieee.numeric_std.ALL; +-- Entity: all_pass_filter +-- Purpose: A pass-through filter that maintains the same interface and timing +-- characteristics as a moving average filter but passes data unchanged. +-- This is useful for testing or as a baseline comparison. ENTITY all_pass_filter IS - GENERIC ( - TDATA_WIDTH : POSITIVE := 24 - ); - PORT ( - aclk : IN STD_LOGIC; - aresetn : IN STD_LOGIC; + GENERIC ( + TDATA_WIDTH : POSITIVE := 24 -- Width of the data bus in bits + ); + PORT ( + -- Clock and reset signals + aclk : IN STD_LOGIC; -- Main clock input + aresetn : IN STD_LOGIC; -- Active-low asynchronous reset - s_axis_tvalid : IN STD_LOGIC; - s_axis_tdata : IN STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); - s_axis_tlast : IN STD_LOGIC; - s_axis_tready : OUT STD_LOGIC; + -- AXI4-Stream Slave Interface (Input) + s_axis_tvalid : IN STD_LOGIC; -- Input data valid signal + s_axis_tdata : IN STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); -- Input data + s_axis_tlast : IN STD_LOGIC; -- Input end-of-packet signal + s_axis_tready : OUT STD_LOGIC; -- Ready to accept input data - m_axis_tvalid : OUT STD_LOGIC; - m_axis_tdata : OUT STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); - m_axis_tlast : OUT STD_LOGIC; - m_axis_tready : IN STD_LOGIC - ); + -- AXI4-Stream Master Interface (Output) + m_axis_tvalid : OUT STD_LOGIC; -- Output data valid signal + m_axis_tdata : OUT STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); -- Output data + m_axis_tlast : OUT STD_LOGIC; -- Output end-of-packet signal + m_axis_tready : IN STD_LOGIC -- Downstream ready signal + ); END all_pass_filter; ARCHITECTURE Behavioral OF all_pass_filter IS - SIGNAL trigger : STD_LOGIC := '0'; + -- Internal control signal to trigger data output after input processing + SIGNAL trigger : STD_LOGIC := '0'; - SIGNAL s_axis_tready_int : STD_LOGIC := '0'; - SIGNAL s_axis_tlast_reg : STD_LOGIC := '0'; - SIGNAL m_axis_tdata_temp : STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0) := (OTHERS => '0'); - SIGNAL m_axis_tvalid_int : STD_LOGIC := '0'; + -- Internal slave interface signals + SIGNAL s_axis_tready_int : STD_LOGIC := '0'; -- Internal ready signal for input + SIGNAL s_axis_tlast_reg : STD_LOGIC := '0'; -- Registered version of input tlast + + -- Internal data storage and master interface signals + SIGNAL m_axis_tdata_temp : STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0) := (OTHERS => '0'); -- Temporary storage for input data + SIGNAL m_axis_tvalid_int : STD_LOGIC := '0'; -- Internal valid signal for output BEGIN - -- This architecture mimics the structure, handshake logic, and timing (clock cycles spent) + -- Architecture Overview: + -- This design mimics the structure, handshake logic, and timing (clock cycles spent) -- of the moving_average_filter, but does not process or modify the samples. -- It simply passes input data to the output unchanged, ensuring the same latency and interface behavior. - -- Assigning the output signals - m_axis_tvalid <= m_axis_tvalid_int; - s_axis_tready <= s_axis_tready_int; + -- Data Flow: + -- 1. Input data is captured when s_axis_tvalid and s_axis_tready are both high + -- 2. Data is stored in temporary registers and a trigger is set + -- 3. On the next clock cycle, if output interface is ready, data is output + -- 4. This creates a 1-clock cycle latency matching the moving average filter - PROCESS (aclk) - BEGIN - IF rising_edge(aclk) THEN + -- Connect internal signals to output ports + m_axis_tvalid <= m_axis_tvalid_int; -- Drive output valid with internal signal + s_axis_tready <= s_axis_tready_int; -- Drive input ready with internal signal - IF aresetn = '0' THEN - -- Reset all internal signals - trigger <= '0'; + -- Main processing logic - synchronous to clock + PROCESS (aclk) + BEGIN + IF rising_edge(aclk) THEN - s_axis_tlast_reg <= '0'; - s_axis_tready_int <= '0'; - m_axis_tdata_temp <= (OTHERS => '0'); - m_axis_tvalid_int <= '0'; - m_axis_tlast <= '0'; + -- Asynchronous reset logic (active low) + IF aresetn = '0' THEN + -- Reset all internal signals to known states + trigger <= '0'; -- Clear data processing trigger + s_axis_tlast_reg <= '0'; -- Clear registered tlast + s_axis_tready_int <= '0'; -- Not ready to accept data during reset + m_axis_tdata_temp <= (OTHERS => '0'); -- Clear temporary data storage + m_axis_tvalid_int <= '0'; -- No valid data on output during reset + m_axis_tlast <= '0'; -- Clear output tlast - ELSE - -- Clear valid flag when master interface is ready - IF m_axis_tready = '1' THEN - m_axis_tvalid_int <= '0'; - END IF; + ELSE + -- Normal operation logic - -- Data output logic - IF trigger = '1' AND (m_axis_tvalid_int = '0' OR m_axis_tready = '1') THEN - m_axis_tdata <= m_axis_tdata_temp; - m_axis_tlast <= s_axis_tlast_reg; + -- Output handshake management: + -- Clear the output valid flag when downstream is ready to accept data + -- This allows new data to be output on the next cycle + IF m_axis_tready = '1' THEN + m_axis_tvalid_int <= '0'; + END IF; - m_axis_tvalid_int <= '1'; - trigger <= '0'; - - END IF; + -- Data output logic: + -- Output data when trigger is set AND output interface is available + -- (either no valid data pending OR downstream is ready) + IF trigger = '1' AND (m_axis_tvalid_int = '0' OR m_axis_tready = '1') THEN + -- Transfer stored data to output + m_axis_tdata <= m_axis_tdata_temp; -- Output the stored input data unchanged + m_axis_tlast <= s_axis_tlast_reg; -- Output the registered tlast signal + + -- Set output control signals + m_axis_tvalid_int <= '1'; -- Mark output data as valid + trigger <= '0'; -- Clear trigger - data has been output + END IF; - -- Data input logic - IF s_axis_tvalid = '1' AND s_axis_tready_int = '1' THEN - s_axis_tlast_reg <= s_axis_tlast; - m_axis_tdata_temp <= s_axis_tdata; - - trigger <= '1'; + -- Data input logic: + -- Capture input data when both valid and ready are asserted (AXI handshake) + IF s_axis_tvalid = '1' AND s_axis_tready_int = '1' THEN + -- Store input signals for later output + s_axis_tlast_reg <= s_axis_tlast; -- Register the tlast signal + m_axis_tdata_temp <= s_axis_tdata; -- Store input data (pass-through, no processing) + + -- Set trigger to indicate data is ready for output + trigger <= '1'; + END IF; - END IF; + -- Input ready logic: + -- Ready to accept new input when: + -- - Downstream is ready (can immediately pass data through), OR + -- - No valid data is pending on output (have buffer space) + -- This implements backpressure - if output is blocked, input will be blocked + s_axis_tready_int <= m_axis_tready OR NOT m_axis_tvalid_int; - s_axis_tready_int <= m_axis_tready OR NOT m_axis_tvalid_int; + END IF; - END IF; + END IF; - END IF; - - END PROCESS; + END PROCESS; END Behavioral; \ No newline at end of file diff --git a/LAB3/src/balance_controller.vhd b/LAB3/src/balance_controller.vhd index 9941019..4586d60 100644 --- a/LAB3/src/balance_controller.vhd +++ b/LAB3/src/balance_controller.vhd @@ -2,112 +2,146 @@ LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; USE ieee.numeric_std.ALL; +-- Entity: balance_controller +-- Purpose: Controls the stereo balance (left/right channel volume) of audio data +-- based on a balance control input. Implements variable attenuation +-- using bit shifting for efficient hardware implementation. ENTITY balance_controller IS - GENERIC ( - TDATA_WIDTH : POSITIVE := 24; - BALANCE_WIDTH : POSITIVE := 10; - BALANCE_STEP_2 : POSITIVE := 6 -- i.e., balance_values_per_step = 2**BALANCE_STEP_2 - ); - PORT ( - aclk : IN STD_LOGIC; - aresetn : IN STD_LOGIC; + GENERIC ( + TDATA_WIDTH : POSITIVE := 24; -- Width of audio data bus (24-bit audio samples) + BALANCE_WIDTH : POSITIVE := 10; -- Width of balance control input (10-bit = 0-1023 range) + BALANCE_STEP_2 : POSITIVE := 6 -- Log2 of balance values per step (2^6 = 64 values per step) + ); + PORT ( + -- Clock and reset + aclk : IN STD_LOGIC; -- Main clock + aresetn : IN STD_LOGIC; -- Active-low asynchronous reset - s_axis_tvalid : IN STD_LOGIC; - s_axis_tdata : IN STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); - s_axis_tready : OUT STD_LOGIC; - s_axis_tlast : IN STD_LOGIC; + -- AXI4-Stream Slave Interface (Audio Input) + s_axis_tvalid : IN STD_LOGIC; -- Input data valid + s_axis_tdata : IN STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); -- Audio sample data + s_axis_tready : OUT STD_LOGIC; -- Ready to accept input + s_axis_tlast : IN STD_LOGIC; -- Channel indicator (0=left, 1=right) - m_axis_tvalid : OUT STD_LOGIC; - m_axis_tdata : OUT STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); - m_axis_tready : IN STD_LOGIC; - m_axis_tlast : OUT STD_LOGIC; + -- AXI4-Stream Master Interface (Audio Output) + m_axis_tvalid : OUT STD_LOGIC; -- Output data valid + m_axis_tdata : OUT STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); -- Balanced audio sample + m_axis_tready : IN STD_LOGIC; -- Downstream ready + m_axis_tlast : OUT STD_LOGIC; -- Channel indicator passthrough - balance : IN STD_LOGIC_VECTOR(BALANCE_WIDTH - 1 DOWNTO 0) - ); + -- Balance Control Input + balance : IN STD_LOGIC_VECTOR(BALANCE_WIDTH - 1 DOWNTO 0) -- Balance position (0=full left, 1023=full right, 512=center) + ); END balance_controller; ARCHITECTURE Behavioral OF balance_controller IS - CONSTANT BALANCE_STEPS : INTEGER := (2 ** (BALANCE_WIDTH - 1)) / (2 ** BALANCE_STEP_2) + 1; - CONSTANT BAL_MID : INTEGER := 2 ** (BALANCE_WIDTH - 1); -- 512 for 10 bit - CONSTANT DEAD_ZONE : INTEGER := (2 ** BALANCE_STEP_2) / 2; + -- Balance calculation constants + CONSTANT BALANCE_STEPS : INTEGER := (2 ** (BALANCE_WIDTH - 1)) / (2 ** BALANCE_STEP_2) + 1; -- Number of attenuation steps (9 steps for 10-bit balance) + CONSTANT BAL_MID : INTEGER := 2 ** (BALANCE_WIDTH - 1); -- Center balance position (512 for 10-bit) + CONSTANT DEAD_ZONE : INTEGER := (2 ** BALANCE_STEP_2) / 2; -- Dead zone around center (32 for step size 64) - SIGNAL left_channel : INTEGER RANGE 0 TO BALANCE_STEPS := 0; - SIGNAL right_channel : INTEGER RANGE 0 TO BALANCE_STEPS := 0; + -- Channel attenuation levels (number of bit shifts for volume reduction) + SIGNAL left_channel : INTEGER RANGE 0 TO BALANCE_STEPS := 0; -- Left channel attenuation (0 = no attenuation, higher = more attenuation) + SIGNAL right_channel : INTEGER RANGE 0 TO BALANCE_STEPS := 0; -- Right channel attenuation (0 = no attenuation, higher = more attenuation) - SIGNAL m_axis_tvalid_int : STD_LOGIC; + -- Internal AXI signals + SIGNAL m_axis_tvalid_int : STD_LOGIC; -- Internal valid signal for output BEGIN - -- Assigning the output signals - m_axis_tvalid <= m_axis_tvalid_int; - s_axis_tready <= (m_axis_tready OR NOT m_axis_tvalid_int) AND aresetn; + -- Connect internal signals to output ports + m_axis_tvalid <= m_axis_tvalid_int; + -- Input ready logic: Ready when downstream is ready OR no valid data pending, AND not in reset + s_axis_tready <= (m_axis_tready OR NOT m_axis_tvalid_int) AND aresetn; - -- Balance to exp process to avoid changing the balance value when multiplying it for the sample data - BALANCE_CALC : PROCESS (aclk) - BEGIN + -- Balance calculation process + -- Converts balance knob position to attenuation levels for each channel + BALANCE_CALC : PROCESS (aclk) + BEGIN - IF rising_edge(aclk) THEN + IF rising_edge(aclk) THEN - IF aresetn = '0' THEN - left_channel <= 0; - right_channel <= 0; + IF aresetn = '0' THEN + -- Reset: No attenuation on either channel + left_channel <= 0; + right_channel <= 0; - ELSE - -- Balance left and right channels - IF to_integer(unsigned(balance)) > (BAL_MID + DEAD_ZONE) THEN - left_channel <= to_integer((unsigned(balance) - to_unsigned(BAL_MID + DEAD_ZONE, balance'length)) SRL BALANCE_STEP_2) + 1; -- +1 due to shift approximation defect - ELSE - left_channel <= 0; - END IF; + ELSE + -- Balance Mapping: + -- balance = 0-479: Left (right channel attenuated) + -- balance = 480-543: Center with dead zone (no attenuation) + -- balance = 544-1023: Right (left channel attenuated) - IF to_integer(unsigned(balance)) < (BAL_MID - DEAD_ZONE) THEN - right_channel <= to_integer((to_unsigned(BAL_MID - DEAD_ZONE, balance'length) - unsigned(balance)) SRL BALANCE_STEP_2) + 1; - ELSE - right_channel <= 0; - END IF; + -- Right-leaning balance: Attenuate left channel + IF to_integer(unsigned(balance)) > (BAL_MID + DEAD_ZONE) THEN + -- Calculate left channel attenuation based on how far right of center + -- Subtract center+deadzone, divide by step size, add 1 for rounding + left_channel <= to_integer((unsigned(balance) - to_unsigned(BAL_MID + DEAD_ZONE, balance'length)) SRL BALANCE_STEP_2) + 1; + ELSE + -- Balance not significantly right of center: no left attenuation + left_channel <= 0; + END IF; - END IF; + -- Left-leaning balance: Attenuate right channel + IF to_integer(unsigned(balance)) < (BAL_MID - DEAD_ZONE) THEN + -- Calculate right channel attenuation based on how far left of center + -- Subtract balance from center-deadzone, divide by step size, add 1 for rounding + right_channel <= to_integer((to_unsigned(BAL_MID - DEAD_ZONE, balance'length) - unsigned(balance)) SRL BALANCE_STEP_2) + 1; + ELSE + -- Balance not significantly left of center: no right attenuation + right_channel <= 0; + END IF; - END IF; + END IF; - END PROCESS BALANCE_CALC; + END IF; - -- Handle AXIS stream - AXIS : PROCESS (aclk) - BEGIN + END PROCESS BALANCE_CALC; - IF rising_edge(aclk) THEN + -- AXI4-Stream data processing + -- Applies calculated attenuation to audio samples based on channel + AXIS : PROCESS (aclk) + BEGIN - IF aresetn = '0' THEN - m_axis_tvalid_int <= '0'; - m_axis_tlast <= '0'; + IF rising_edge(aclk) THEN - ELSE - -- Clear valid flag when master interface is ready - IF m_axis_tready = '1' THEN - m_axis_tvalid_int <= '0'; - END IF; + IF aresetn = '0' THEN + -- Reset output interface + m_axis_tvalid_int <= '0'; -- No valid output data + m_axis_tlast <= '0'; -- Clear channel indicator - -- Handle the data flow - IF s_axis_tvalid = '1' AND m_axis_tready = '1' THEN - -- Joystick datasheet: (x-axis) a 0 value corresponds to the axis - -- being tilted fully to the left and a value of 1023 - -- corresponds fully to the right - IF s_axis_tlast = '0' THEN -- left - m_axis_tdata <= STD_LOGIC_VECTOR(shift_right(signed(s_axis_tdata), left_channel)); - ELSE -- right - m_axis_tdata <= STD_LOGIC_VECTOR(shift_right(signed(s_axis_tdata), right_channel)); - END IF; + ELSE + -- Output handshake: Clear valid flag when downstream accepts data + IF m_axis_tready = '1' THEN + m_axis_tvalid_int <= '0'; + END IF; - m_axis_tvalid_int <= '1'; - m_axis_tlast <= s_axis_tlast; + -- Data processing: Apply balance when input and output are ready + IF s_axis_tvalid = '1' AND m_axis_tready = '1' THEN + -- Channel identification based on tlast signal: + -- tlast = '0': Left channel sample + -- tlast = '1': Right channel sample + + IF s_axis_tlast = '0' THEN + -- Left channel: Apply left channel attenuation + -- Arithmetic right shift preserves sign for signed audio data + m_axis_tdata <= STD_LOGIC_VECTOR(shift_right(signed(s_axis_tdata), left_channel)); + ELSE + -- Right channel: Apply right channel attenuation + -- Arithmetic right shift preserves sign for signed audio data + m_axis_tdata <= STD_LOGIC_VECTOR(shift_right(signed(s_axis_tdata), right_channel)); + END IF; - END IF; + -- Set output control signals + m_axis_tvalid_int <= '1'; -- Mark output as valid + m_axis_tlast <= s_axis_tlast; -- Pass through channel indicator - END IF; + END IF; - END IF; + END IF; - END PROCESS AXIS; + END IF; + + END PROCESS AXIS; END Behavioral; \ No newline at end of file diff --git a/LAB3/src/digilent_jstk2.vhd b/LAB3/src/digilent_jstk2.vhd index 4fa024b..6c9599f 100644 --- a/LAB3/src/digilent_jstk2.vhd +++ b/LAB3/src/digilent_jstk2.vhd @@ -1,180 +1,200 @@ LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; +-- Entity: digilent_jstk2 +-- Purpose: Interface controller for the Digilent JSTK2 joystick module via SPI +-- Sends LED color commands and receives joystick/button data ENTITY digilent_jstk2 IS - GENERIC ( - DELAY_US : INTEGER := 225; -- Delay (in us) between two packets - -- 25us Required by the SPI IP-Core doesn't work, - -- it requires another SPI clock cycle - CLKFREQ : INTEGER := 100_000_000; -- Frequency of the aclk signal (in Hz) - SPI_SCLKFREQ : INTEGER := 5_000 -- Frequency of the SPI SCLK clock signal (in Hz) - ); - PORT ( - aclk : IN STD_LOGIC; - aresetn : IN STD_LOGIC; + GENERIC ( + DELAY_US : INTEGER := 225; -- Delay (in microseconds) between two SPI packets + -- 25us required by SPI IP-Core doesn't work, + -- it requires another SPI clock cycle + CLKFREQ : INTEGER := 100_000_000; -- Frequency of the aclk signal (in Hz) + SPI_SCLKFREQ : INTEGER := 5_000 -- Frequency of the SPI SCLK clock signal (in Hz) + ); + PORT ( + aclk : IN STD_LOGIC; -- Main clock input + aresetn : IN STD_LOGIC; -- Active-low asynchronous reset - -- Data going TO the SPI IP-Core (and so, to the JSTK2 module) - m_axis_tvalid : OUT STD_LOGIC; - m_axis_tdata : OUT STD_LOGIC_VECTOR(7 DOWNTO 0); - m_axis_tready : IN STD_LOGIC; + -- AXI4-Stream Master Interface: Data going TO the SPI IP-Core (and so, to the JSTK2 module) + m_axis_tvalid : OUT STD_LOGIC; -- Output data valid signal + m_axis_tdata : OUT STD_LOGIC_VECTOR(7 DOWNTO 0); -- 8-bit data to send via SPI + m_axis_tready : IN STD_LOGIC; -- SPI IP-Core ready to accept data - -- Data coming FROM the SPI IP-Core (and so, from the JSTK2 module) - -- There is no tready signal, so you must be always ready to accept and use the incoming data, or it will be lost! - s_axis_tvalid : IN STD_LOGIC; - s_axis_tdata : IN STD_LOGIC_VECTOR(7 DOWNTO 0); + -- AXI4-Stream Slave Interface: Data coming FROM the SPI IP-Core (and so, from the JSTK2 module) + -- Note: There is no tready signal, so you must be always ready to accept incoming data, or it will be lost! + s_axis_tvalid : IN STD_LOGIC; -- Input data valid signal + s_axis_tdata : IN STD_LOGIC_VECTOR(7 DOWNTO 0); -- 8-bit data received via SPI - -- Joystick and button values read from the module - jstk_x : OUT STD_LOGIC_VECTOR(9 DOWNTO 0); - jstk_y : OUT STD_LOGIC_VECTOR(9 DOWNTO 0); - btn_jstk : OUT STD_LOGIC; - btn_trigger : OUT STD_LOGIC; + -- Joystick and button values read from the JSTK2 module + jstk_x : OUT STD_LOGIC_VECTOR(9 DOWNTO 0); -- X-axis joystick position (10-bit, 0-1023) + jstk_y : OUT STD_LOGIC_VECTOR(9 DOWNTO 0); -- Y-axis joystick position (10-bit, 0-1023) + btn_jstk : OUT STD_LOGIC; -- Joystick button state (1=pressed) + btn_trigger : OUT STD_LOGIC; -- Trigger button state (1=pressed) - -- LED color to send to the module - led_r : IN STD_LOGIC_VECTOR(7 DOWNTO 0); - led_g : IN STD_LOGIC_VECTOR(7 DOWNTO 0); - led_b : IN STD_LOGIC_VECTOR(7 DOWNTO 0) - ); + -- LED color values to send to the JSTK2 module + led_r : IN STD_LOGIC_VECTOR(7 DOWNTO 0); -- Red LED intensity (0-255) + led_g : IN STD_LOGIC_VECTOR(7 DOWNTO 0); -- Green LED intensity (0-255) + led_b : IN STD_LOGIC_VECTOR(7 DOWNTO 0) -- Blue LED intensity (0-255) + ); END digilent_jstk2; ARCHITECTURE Behavioral OF digilent_jstk2 IS - -- Code for the SetLEDRGB command, see the JSTK2 datasheet. - CONSTANT CMDSETLEDRGB : STD_LOGIC_VECTOR(7 DOWNTO 0) := x"84"; + -- Command code for the SetLEDRGB command, see the JSTK2 datasheet + CONSTANT CMDSETLEDRGB : STD_LOGIC_VECTOR(7 DOWNTO 0) := x"84"; - -- Do not forget that you MUST wait a bit between two packets. See the JSTK2 datasheet (and the SPI IP-Core README). - ------------------------------------------------------------ + -- Calculate delay in clock cycles based on microsecond delay and clock frequency + -- Important: You MUST wait between two SPI packets (see JSTK2 datasheet and SPI IP-Core README) + CONSTANT DELAY_CLK_CYCLES : INTEGER := DELAY_US * (CLKFREQ / 1_000_000); - CONSTANT DELAY_CLK_CYCLES : INTEGER := DELAY_US * (CLKFREQ / 1_000_000); + -- State machine type definitions + TYPE tx_state_type IS (DELAY, SEND_CMD, SEND_RED, SEND_GREEN, SEND_BLUE, SEND_DUMMY); + TYPE rx_state_type IS (JSTK_X_LOW, JSTK_X_HIGH, JSTK_Y_LOW, JSTK_Y_HIGH, BUTTONS); - -- State machine states - TYPE tx_state_type IS (DELAY, SEND_CMD, SEND_RED, SEND_GREEN, SEND_BLUE, SEND_DUMMY); - TYPE rx_state_type IS (JSTK_X_LOW, JSTK_X_HIGH, JSTK_Y_LOW, JSTK_Y_HIGH, BUTTONS); + -- State machine signals + SIGNAL tx_state : tx_state_type := DELAY; -- Transmit state machine current state + SIGNAL rx_state : rx_state_type := JSTK_X_LOW; -- Receive state machine current state - SIGNAL tx_state : tx_state_type := DELAY; - SIGNAL rx_state : rx_state_type := JSTK_X_LOW; - - SIGNAL tx_delay_counter : INTEGER := 0; - - SIGNAL rx_cache : STD_LOGIC_VECTOR(7 DOWNTO 0); + -- Timing and data storage signals + SIGNAL tx_delay_counter : INTEGER := 0; -- Counter for inter-packet delay timing + SIGNAL rx_cache : STD_LOGIC_VECTOR(7 DOWNTO 0); -- Temporary storage for multi-byte data reception BEGIN - -- The SPI IP-Core is a slave, so we must set the m_axis_tvalid signal to '1' when we want to send data to it. - WITH tx_state SELECT m_axis_tvalid <= - '0' WHEN DELAY, - '1' WHEN SEND_CMD, - '1' WHEN SEND_RED, - '1' WHEN SEND_GREEN, - '1' WHEN SEND_BLUE, - '1' WHEN SEND_DUMMY; - -- Send the data to the SPI IP-Core based on the current state of the TX FSM - WITH tx_state SELECT m_axis_tdata <= - (OTHERS => '0') WHEN DELAY, - CMDSETLEDRGB WHEN SEND_CMD, - led_r WHEN SEND_RED, - led_g WHEN SEND_GREEN, - led_b WHEN SEND_BLUE, - "01101000" WHEN SEND_DUMMY; -- Dummy byte + -- Output valid signal control: Set to '1' when we want to send data to SPI IP-Core + -- Only inactive during DELAY state when waiting between packets + WITH tx_state SELECT m_axis_tvalid <= + '0' WHEN DELAY, -- No transmission during delay + '1' WHEN SEND_CMD, -- Send command byte + '1' WHEN SEND_RED, -- Send red LED value + '1' WHEN SEND_GREEN, -- Send green LED value + '1' WHEN SEND_BLUE, -- Send blue LED value + '1' WHEN SEND_DUMMY; -- Send dummy byte to complete transaction - -- TX FSM: invia un nuovo comando solo dopo che la risposta precedente è stata ricevuta (rx_done = '1') - TX : PROCESS (aclk) - BEGIN - IF rising_edge(aclk) THEN - IF aresetn = '0' THEN + -- Output data multiplexer: Select what data to send based on current TX state + WITH tx_state SELECT m_axis_tdata <= + (OTHERS => '0') WHEN DELAY, -- No data during delay + CMDSETLEDRGB WHEN SEND_CMD, -- SetLEDRGB command (0x84) + led_r WHEN SEND_RED, -- Red LED intensity value + led_g WHEN SEND_GREEN, -- Green LED intensity value + led_b WHEN SEND_BLUE, -- Blue LED intensity value + "01101001" WHEN SEND_DUMMY; -- Dummy byte to complete 5-byte transaction - tx_state <= DELAY; - tx_delay_counter <= 0; + -- TX State Machine: Sends LED color commands to JSTK2 module + -- Protocol: Command(1) + Red(1) + Green(1) + Blue(1) + Dummy(1) = 5 bytes total + TX : PROCESS (aclk) + BEGIN + IF rising_edge(aclk) THEN + IF aresetn = '0' THEN + -- Reset: Start in delay state with counter cleared + tx_state <= DELAY; + tx_delay_counter <= 0; - ELSE + ELSE - CASE tx_state IS + CASE tx_state IS - WHEN DELAY => - IF tx_delay_counter >= DELAY_CLK_CYCLES THEN - tx_delay_counter <= 0; - tx_state <= SEND_CMD; - ELSE - tx_delay_counter <= tx_delay_counter + 1; - END IF; + WHEN DELAY => + -- Wait for required delay period between SPI transactions + IF tx_delay_counter >= DELAY_CLK_CYCLES THEN + tx_delay_counter <= 0; -- Reset counter + tx_state <= SEND_CMD; -- Start new transmission + ELSE + tx_delay_counter <= tx_delay_counter + 1; -- Continue counting + END IF; - WHEN SEND_CMD => - IF m_axis_tready = '1' THEN - tx_state <= SEND_RED; - END IF; + WHEN SEND_CMD => + -- Send SetLEDRGB command byte + IF m_axis_tready = '1' THEN + tx_state <= SEND_RED; -- Move to red LED transmission + END IF; - WHEN SEND_RED => - IF m_axis_tready = '1' THEN - tx_state <= SEND_GREEN; - END IF; + WHEN SEND_RED => + -- Send red LED intensity value + IF m_axis_tready = '1' THEN + tx_state <= SEND_GREEN; -- Move to green LED transmission + END IF; - WHEN SEND_GREEN => - IF m_axis_tready = '1' THEN - tx_state <= SEND_BLUE; - END IF; + WHEN SEND_GREEN => + -- Send green LED intensity value + IF m_axis_tready = '1' THEN + tx_state <= SEND_BLUE; -- Move to blue LED transmission + END IF; - WHEN SEND_BLUE => - IF m_axis_tready = '1' THEN - tx_state <= SEND_DUMMY; - END IF; + WHEN SEND_BLUE => + -- Send blue LED intensity value + IF m_axis_tready = '1' THEN + tx_state <= SEND_DUMMY; -- Move to dummy byte transmission + END IF; - WHEN SEND_DUMMY => - IF m_axis_tready = '1' THEN - tx_state <= DELAY; - END IF; + WHEN SEND_DUMMY => + -- Send dummy byte to complete 5-byte transaction + IF m_axis_tready = '1' THEN + tx_state <= DELAY; -- Return to delay state + END IF; - END CASE; - END IF; - END IF; - END PROCESS TX; + END CASE; + END IF; + END IF; + END PROCESS TX; - -- RX FSM: riceve 5 byte, aggiorna le uscite e segnala a TX FSM quando la risposta è completa - RX : PROCESS (aclk) - BEGIN - IF rising_edge(aclk) THEN + -- RX State Machine: Receives 5 bytes of response data and updates outputs + -- Protocol: X_low(1) + X_high(1) + Y_low(1) + Y_high(1) + Buttons(1) = 5 bytes total + RX : PROCESS (aclk) + BEGIN + IF rising_edge(aclk) THEN - IF aresetn = '0' THEN + IF aresetn = '0' THEN + -- Reset: Start waiting for X-axis low byte + rx_state <= JSTK_X_LOW; + rx_cache <= (OTHERS => '0'); - rx_state <= JSTK_X_LOW; - rx_cache <= (OTHERS => '0'); + ELSE - ELSE + CASE rx_state IS - CASE rx_state IS + WHEN JSTK_X_LOW => + -- Receive X-axis low byte (bits 7:0) + IF s_axis_tvalid = '1' THEN + rx_cache <= s_axis_tdata; -- Store low byte temporarily + rx_state <= JSTK_X_HIGH; -- Wait for high byte + END IF; - WHEN JSTK_X_LOW => - IF s_axis_tvalid = '1' THEN - rx_cache <= s_axis_tdata; - rx_state <= JSTK_X_HIGH; - END IF; + WHEN JSTK_X_HIGH => + -- Receive X-axis high byte (bits 9:8) and assemble complete X value + IF s_axis_tvalid = '1' THEN + -- Combine: high_byte(1:0) & low_byte(7:0) = 10-bit X position + jstk_x <= s_axis_tdata(1 DOWNTO 0) & rx_cache; + rx_state <= JSTK_Y_LOW; -- Move to Y-axis reception + END IF; - WHEN JSTK_X_HIGH => - IF s_axis_tvalid = '1' THEN - jstk_x <= s_axis_tdata(1 DOWNTO 0) & rx_cache; - rx_state <= JSTK_Y_LOW; - END IF; + WHEN JSTK_Y_LOW => + -- Receive Y-axis low byte (bits 7:0) + IF s_axis_tvalid = '1' THEN + rx_cache <= s_axis_tdata; -- Store low byte temporarily + rx_state <= JSTK_Y_HIGH; -- Wait for high byte + END IF; - WHEN JSTK_Y_LOW => - IF s_axis_tvalid = '1' THEN - rx_cache <= s_axis_tdata; - rx_state <= JSTK_Y_HIGH; - END IF; + WHEN JSTK_Y_HIGH => + -- Receive Y-axis high byte (bits 9:8) and assemble complete Y value + IF s_axis_tvalid = '1' THEN + -- Combine: high_byte(1:0) & low_byte(7:0) = 10-bit Y position + jstk_y <= s_axis_tdata(1 DOWNTO 0) & rx_cache; + rx_state <= BUTTONS; -- Move to button reception + END IF; - WHEN JSTK_Y_HIGH => - IF s_axis_tvalid = '1' THEN - jstk_y <= s_axis_tdata(1 DOWNTO 0) & rx_cache; - rx_state <= BUTTONS; - END IF; + WHEN BUTTONS => + -- Receive button states byte + IF s_axis_tvalid = '1' THEN + btn_jstk <= s_axis_tdata(0); -- Joystick button (bit 0) + btn_trigger <= s_axis_tdata(1); -- Trigger button (bit 1) + rx_state <= JSTK_X_LOW; -- Return to start for next packet + END IF; - WHEN BUTTONS => - IF s_axis_tvalid = '1' THEN - btn_jstk <= s_axis_tdata(0); - btn_trigger <= s_axis_tdata(1); - rx_state <= JSTK_X_LOW; - END IF; - - END CASE; - END IF; - END IF; - END PROCESS RX; + END CASE; + END IF; + END IF; + END PROCESS RX; END ARCHITECTURE; \ No newline at end of file diff --git a/LAB3/src/effect_selector.vhd b/LAB3/src/effect_selector.vhd index 3936ea5..21eb378 100644 --- a/LAB3/src/effect_selector.vhd +++ b/LAB3/src/effect_selector.vhd @@ -1,20 +1,27 @@ LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; +-- Entity: effect_selector +-- Purpose: Routes joystick input to different audio control parameters based on effect mode +-- Acts as a multiplexer/router for joystick control signals ENTITY effect_selector IS GENERIC ( - JOYSTICK_LENGHT : INTEGER := 10 + JOYSTICK_LENGHT : INTEGER := 10 -- Width of joystick position data (10-bit = 0-1023 range) ); PORT ( - aclk : IN STD_LOGIC; - aresetn : IN STD_LOGIC; + -- Clock and reset signals + aclk : IN STD_LOGIC; -- Main clock input + aresetn : IN STD_LOGIC; -- Active-low asynchronous reset - effect : IN STD_LOGIC; - jstck_x : IN STD_LOGIC_VECTOR(JOYSTICK_LENGHT - 1 DOWNTO 0); - jstck_y : IN STD_LOGIC_VECTOR(JOYSTICK_LENGHT - 1 DOWNTO 0); - volume : OUT STD_LOGIC_VECTOR(JOYSTICK_LENGHT - 1 DOWNTO 0); - balance : OUT STD_LOGIC_VECTOR(JOYSTICK_LENGHT - 1 DOWNTO 0); - lfo_period : OUT STD_LOGIC_VECTOR(JOYSTICK_LENGHT - 1 DOWNTO 0) + -- Control and input signals + effect : IN STD_LOGIC; -- Effect mode selector (0=volume/balance mode, 1=LFO/balance mode) + jstck_x : IN STD_LOGIC_VECTOR(JOYSTICK_LENGHT - 1 DOWNTO 0); -- X-axis joystick position + jstck_y : IN STD_LOGIC_VECTOR(JOYSTICK_LENGHT - 1 DOWNTO 0); -- Y-axis joystick position + + -- Output control parameters + volume : OUT STD_LOGIC_VECTOR(JOYSTICK_LENGHT - 1 DOWNTO 0); -- Volume control output + balance : OUT STD_LOGIC_VECTOR(JOYSTICK_LENGHT - 1 DOWNTO 0); -- Balance control output + lfo_period : OUT STD_LOGIC_VECTOR(JOYSTICK_LENGHT - 1 DOWNTO 0) -- LFO period control output ); END effect_selector; @@ -22,27 +29,39 @@ ARCHITECTURE Behavioral OF effect_selector IS BEGIN + -- Main control logic process + -- Routes joystick axes to appropriate audio control parameters based on selected mode PROCESS (aclk) BEGIN IF rising_edge(aclk) THEN IF aresetn = '0' THEN - volume <= (OTHERS => '0'); - balance <= (OTHERS => '0'); - lfo_period <= (OTHERS => '0'); + -- Reset all outputs to zero (minimum values) + volume <= (OTHERS => '0'); -- Minimum volume + balance <= (OTHERS => '0'); -- Full left balance + lfo_period <= (OTHERS => '0'); -- Minimum LFO period ELSE + -- Normal operation: Route joystick inputs based on effect mode + + -- X-axis always controls balance regardless of mode + -- This provides consistent left/right audio balance control balance <= jstck_x; + -- Y-axis control depends on selected effect mode IF effect = '1' THEN - -- LFO control + -- LFO Mode: Y-axis controls Low Frequency Oscillator period + -- Used for tremolo, vibrato, or other modulation effects lfo_period <= jstck_y; + -- Volume remains at last set value (not updated in LFO mode) ELSE - -- volume/balance control + -- Volume/Balance Mode: Y-axis controls overall volume level + -- Traditional audio mixer control mode volume <= jstck_y; - + -- LFO period remains at last set value (not updated in volume mode) + END IF; END IF; diff --git a/LAB3/src/led_controller.vhd b/LAB3/src/led_controller.vhd index ebeae91..8c7e198 100644 --- a/LAB3/src/led_controller.vhd +++ b/LAB3/src/led_controller.vhd @@ -1,37 +1,64 @@ LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; +-- Entity: led_controller +-- Purpose: Controls RGB LED indicators based on audio system status +-- Provides visual feedback for mute and filter enable states ENTITY led_controller IS - GENERIC ( - LED_WIDTH : POSITIVE := 8 - ); - PORT ( - mute_enable : IN STD_LOGIC; - filter_enable : IN STD_LOGIC; + GENERIC ( + LED_WIDTH : POSITIVE := 8 -- Width of LED intensity control (8-bit = 0-255 intensity levels) + ); + PORT ( + -- Control input signals + mute_enable : IN STD_LOGIC; -- Mute status (1=audio muted, 0=audio active) + filter_enable : IN STD_LOGIC; -- Filter status (1=filter active, 0=filter bypassed) - led_r : OUT STD_LOGIC_VECTOR(LED_WIDTH - 1 DOWNTO 0); - led_g : OUT STD_LOGIC_VECTOR(LED_WIDTH - 1 DOWNTO 0); - led_b : OUT STD_LOGIC_VECTOR(LED_WIDTH - 1 DOWNTO 0) - ); + -- RGB LED output signals (intensity control) + led_r : OUT STD_LOGIC_VECTOR(LED_WIDTH - 1 DOWNTO 0); -- Red LED intensity (0-255) + led_g : OUT STD_LOGIC_VECTOR(LED_WIDTH - 1 DOWNTO 0); -- Green LED intensity (0-255) + led_b : OUT STD_LOGIC_VECTOR(LED_WIDTH - 1 DOWNTO 0) -- Blue LED intensity (0-255) + ); END led_controller; ARCHITECTURE Behavioral OF led_controller IS - -- constant for "ON" and "OFF" - CONSTANT ALL_ON : STD_LOGIC_VECTOR(LED_WIDTH - 1 DOWNTO 0) := (OTHERS => '1'); - CONSTANT ALL_OFF : STD_LOGIC_VECTOR(LED_WIDTH - 1 DOWNTO 0) := (OTHERS => '0'); + -- Constants for LED intensity levels + CONSTANT ALL_ON : STD_LOGIC_VECTOR(LED_WIDTH - 1 DOWNTO 0) := (OTHERS => '1'); -- Maximum brightness (255) + CONSTANT ALL_OFF : STD_LOGIC_VECTOR(LED_WIDTH - 1 DOWNTO 0) := (OTHERS => '0'); -- LED off (0) BEGIN - -- If mute_enable = '1' Red LEDs on, others off - -- Else if filter_enable='1' Blue LEDs on, others off - -- Else Green LEDs on, others off + -- LED Status Indication Logic: + -- Priority-based color coding for system status + -- + -- Color Scheme: + -- RED = Mute active (highest priority - audio completely off) + -- BLUE = Filter active (medium priority - audio processing enabled) + -- GREEN = Normal operation (lowest priority - audio pass-through) - led_r <= ALL_ON WHEN mute_enable = '1' ELSE - ALL_OFF; - led_b <= ALL_ON WHEN (mute_enable = '0' AND filter_enable = '1') ELSE - ALL_OFF; - led_g <= ALL_ON WHEN (mute_enable = '0' AND filter_enable = '0') ELSE - ALL_OFF; + -- Red LED Control: Indicates mute status + -- Turn on red LED when audio is muted, regardless of filter state + led_r <= ALL_ON WHEN mute_enable = '1' ELSE + ALL_OFF; + + -- Blue LED Control: Indicates filter activation + -- Turn on blue LED when filter is active AND audio is not muted + -- Mute has higher priority than filter indication + led_b <= ALL_ON WHEN (mute_enable = '0' AND filter_enable = '1') ELSE + ALL_OFF; + + -- Green LED Control: Indicates normal operation + -- Turn on green LED when audio is active (not muted) AND filter is disabled + -- This represents the default "audio pass-through" state + led_g <= ALL_ON WHEN (mute_enable = '0' AND filter_enable = '0') ELSE + ALL_OFF; + + -- Truth Table for LED States: + -- mute_enable | filter_enable | RED | GREEN | BLUE | Status + -- ------------|---------------|-----|-------|------|------------------ + -- 0 | 0 | 0 | 1 | 0 | Normal (Green) + -- 0 | 1 | 0 | 0 | 1 | Filter On (Blue) + -- 1 | 0 | 1 | 0 | 0 | Muted (Red) + -- 1 | 1 | 1 | 0 | 0 | Muted (Red) END Behavioral; \ No newline at end of file diff --git a/LAB3/src/led_level_controller.vhd b/LAB3/src/led_level_controller.vhd index 8558470..004f664 100644 --- a/LAB3/src/led_level_controller.vhd +++ b/LAB3/src/led_level_controller.vhd @@ -2,121 +2,201 @@ LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; USE IEEE.NUMERIC_STD.ALL; +-- Entity: led_level_controller +-- Purpose: Audio level meter using LEDs to display real-time audio amplitude +-- Processes stereo audio samples and drives a bar graph LED display +-- Provides visual feedback of audio signal strength for both channels combined ENTITY led_level_controller IS GENERIC ( - NUM_LEDS : POSITIVE := 16; - CHANNEL_LENGHT : POSITIVE := 24; - refresh_time_ms : POSITIVE := 1; - clock_period_ns : POSITIVE := 10 + NUM_LEDS : POSITIVE := 16; -- Number of LEDs in the level meter display + CHANNEL_LENGHT : POSITIVE := 24; -- Width of audio data (24-bit audio samples) + refresh_time_ms : POSITIVE := 1; -- LED refresh rate in milliseconds (1ms = 1kHz update rate) + clock_period_ns : POSITIVE := 10 -- System clock period in nanoseconds (10ns = 100MHz) ); PORT ( + -- Clock and reset signals + aclk : IN STD_LOGIC; -- Main clock input + aresetn : IN STD_LOGIC; -- Active-low asynchronous reset - aclk : IN STD_LOGIC; - aresetn : IN STD_LOGIC; - - led : OUT STD_LOGIC_VECTOR(NUM_LEDS - 1 DOWNTO 0); - - s_axis_tvalid : IN STD_LOGIC; - s_axis_tdata : IN STD_LOGIC_VECTOR(CHANNEL_LENGHT - 1 DOWNTO 0); - s_axis_tlast : IN STD_LOGIC; - s_axis_tready : OUT STD_LOGIC + -- LED output array (bar graph display) + led : OUT STD_LOGIC_VECTOR(NUM_LEDS - 1 DOWNTO 0); -- LED control signals (1=on, 0=off) + -- AXI4-Stream Slave 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=right, 1=left) + s_axis_tready : OUT STD_LOGIC -- Always ready to accept data ); END led_level_controller; ARCHITECTURE Behavioral OF led_level_controller IS + + -- Calculate number of clock cycles for LED refresh timing + -- Example: 1ms refresh at 100MHz = (1*1,000,000)/10 = 100,000 cycles CONSTANT REFRESH_CYCLES : NATURAL := (refresh_time_ms * 1_000_000) / clock_period_ns; - SIGNAL volume_value : signed(CHANNEL_LENGHT - 1 DOWNTO 0) := (OTHERS => '0'); - SIGNAL abs_audio_left : unsigned(CHANNEL_LENGHT - 2 DOWNTO 0) := (OTHERS => '0'); - SIGNAL abs_audio_right : unsigned(CHANNEL_LENGHT - 2 DOWNTO 0) := (OTHERS => '0'); - SIGNAL leds_int : STD_LOGIC_VECTOR(NUM_LEDS - 1 DOWNTO 0) := (OTHERS => '0'); - SIGNAL led_update : STD_LOGIC := '0'; - SIGNAL refresh_counter : NATURAL RANGE 0 TO REFRESH_CYCLES - 1 := 0; + -- Audio processing signals + SIGNAL volume_value : signed(CHANNEL_LENGHT - 1 DOWNTO 0) := (OTHERS => '0'); -- Current audio sample (signed) + SIGNAL abs_audio_left : unsigned(CHANNEL_LENGHT - 2 DOWNTO 0) := (OTHERS => '0'); -- Absolute value of left channel + SIGNAL abs_audio_right : unsigned(CHANNEL_LENGHT - 2 DOWNTO 0) := (OTHERS => '0'); -- Absolute value of right channel + + -- LED control signals + SIGNAL leds_int : STD_LOGIC_VECTOR(NUM_LEDS - 1 DOWNTO 0) := (OTHERS => '0'); -- Internal LED state + SIGNAL led_update : STD_LOGIC := '0'; -- Trigger for LED refresh + + -- Timing control + SIGNAL refresh_counter : NATURAL RANGE 0 TO REFRESH_CYCLES - 1 := 0; -- Counter for refresh timing BEGIN - led <= leds_int; - s_axis_tready <= '1'; - -- Registering the absolute audio value + -- Connect internal signals to output ports + led <= leds_int; -- Drive external LEDs with internal state + s_axis_tready <= '1'; -- Always ready to accept audio data (no backpressure) + + -- Audio sample processing and absolute value calculation + -- Converts signed audio samples to unsigned absolute values for level detection PROCESS (aclk) - VARIABLE sdata_signed : signed(CHANNEL_LENGHT - 1 DOWNTO 0); - VARIABLE abs_value : unsigned(CHANNEL_LENGHT - 1 DOWNTO 0); + VARIABLE sdata_signed : signed(CHANNEL_LENGHT - 1 DOWNTO 0); -- Temporary signed audio value + VARIABLE abs_value : unsigned(CHANNEL_LENGHT - 1 DOWNTO 0); -- Temporary absolute value BEGIN + IF rising_edge(aclk) THEN + IF aresetn = '0' THEN - volume_value <= (OTHERS => '0'); - abs_audio_left <= (OTHERS => '0'); - abs_audio_right <= (OTHERS => '0'); + -- Reset: Clear all audio processing signals + volume_value <= (OTHERS => '0'); -- Clear current sample + abs_audio_left <= (OTHERS => '0'); -- Clear left channel level + abs_audio_right <= (OTHERS => '0'); -- Clear right channel level + ELSIF s_axis_tvalid = '1' THEN - sdata_signed := signed(s_axis_tdata); - volume_value <= sdata_signed; - -- Absolute value calculation + -- Process new audio sample when valid data is available + sdata_signed := signed(s_axis_tdata); -- Convert input to signed format + volume_value <= sdata_signed; -- Store current sample + + -- Absolute value calculation for amplitude detection + -- Handle two's complement signed numbers correctly IF sdata_signed(CHANNEL_LENGHT - 1) = '1' THEN + -- Negative number: Take two's complement to get absolute value abs_value := unsigned(-sdata_signed); ELSE + -- Positive number: Direct conversion to unsigned abs_value := unsigned(sdata_signed); END IF; - -- Assign to the correct channel - IF s_axis_tlast = '1' THEN -- Left channel + + -- Channel assignment based on tlast signal + -- Note: Channel assignment appears reversed from typical convention + IF s_axis_tlast = '1' THEN + -- tlast = '1': Assign to left channel abs_audio_left <= abs_value(CHANNEL_LENGHT - 2 DOWNTO 0); - ELSE -- Right channel + ELSE + -- tlast = '0': Assign to right channel abs_audio_right <= abs_value(CHANNEL_LENGHT - 2 DOWNTO 0); END IF; + END IF; + END IF; + END PROCESS; - -- Refresh counter + -- LED refresh timing control + -- Generates periodic update signals for smooth LED display updates PROCESS (aclk) BEGIN + IF rising_edge(aclk) THEN + IF aresetn = '0' THEN - refresh_counter <= 0; - led_update <= '0'; + -- Reset timing control + refresh_counter <= 0; -- Clear refresh counter + led_update <= '0'; -- Clear update trigger + ELSIF refresh_counter = REFRESH_CYCLES - 1 THEN - refresh_counter <= 0; - led_update <= '1'; + -- End of refresh period: Trigger LED update + refresh_counter <= 0; -- Reset counter for next period + led_update <= '1'; -- Set update trigger + ELSE - refresh_counter <= refresh_counter + 1; - led_update <= '0'; + -- Continue counting refresh period + refresh_counter <= refresh_counter + 1; -- Increment counter + led_update <= '0'; -- Clear update trigger + END IF; + END IF; + END PROCESS; - -- Linear scaling and LED update + -- LED level calculation and bar graph generation + -- Combines left and right channel levels and maps to LED array PROCESS (aclk) - VARIABLE leds_on : NATURAL RANGE 0 TO NUM_LEDS; - VARIABLE temp_led_level : INTEGER RANGE 0 TO NUM_LEDS; - VARIABLE abs_audio_sum : unsigned(CHANNEL_LENGHT - 1 DOWNTO 0); + VARIABLE leds_on : NATURAL RANGE 0 TO NUM_LEDS; -- Number of LEDs to illuminate + VARIABLE temp_led_level : INTEGER RANGE 0 TO NUM_LEDS; -- Calculated LED level + VARIABLE abs_audio_sum : unsigned(CHANNEL_LENGHT - 1 DOWNTO 0); -- Combined channel amplitude BEGIN + IF rising_edge(aclk) THEN + IF aresetn = '0' THEN + -- Reset: Turn off all LEDs leds_int <= (OTHERS => '0'); + ELSIF led_update = '1' THEN + -- Update LED display when refresh trigger is active + + -- Combine left and right channel amplitudes + -- Resize both channels to full width before addition to prevent overflow abs_audio_sum := resize(abs_audio_left, CHANNEL_LENGHT) + resize(abs_audio_right, CHANNEL_LENGHT); + -- Level calculation with automatic sensitivity scaling IF (abs_audio_left = 0 AND abs_audio_right = 0) THEN + -- Silence: No LEDs illuminated temp_led_level := 0; + ELSE - -- Automatic scaling - -- Sensitivity can be adjusted by changing the shift constant + -- Audio present: Calculate LED level using logarithmic-like scaling + -- Right shift by (CHANNEL_LENGHT - 4) provides automatic sensitivity adjustment + -- The "1 +" ensures at least one LED is on when audio is present + -- Shift amount determines sensitivity: larger shift = less sensitive temp_led_level := 1 + to_integer(shift_right(abs_audio_sum, CHANNEL_LENGHT - 4)); + END IF; - -- Limit to the maximum number of LEDs + -- Limit LED level to available LEDs (prevent array overflow) IF temp_led_level > NUM_LEDS THEN - leds_on := NUM_LEDS; + leds_on := NUM_LEDS; -- Cap at maximum LEDs ELSE - leds_on := temp_led_level; + leds_on := temp_led_level; -- Use calculated level END IF; - -- Update the LEDs - leds_int <= (OTHERS => '0'); + -- Generate bar graph pattern: illuminate LEDs from 0 to (leds_on-1) + -- This creates a classic audio level meter appearance + leds_int <= (OTHERS => '0'); -- Start with all LEDs off + IF leds_on > 0 THEN + -- Turn on LEDs from index 0 up to (leds_on-1) + -- Creates solid bar from bottom to current level leds_int(leds_on - 1 DOWNTO 0) <= (OTHERS => '1'); END IF; + END IF; + END IF; + END PROCESS; + + -- LED Level Meter Operation Summary: + -- 1. Continuously samples stereo audio data + -- 2. Calculates absolute value (amplitude) for each channel + -- 3. Combines left and right channels for total signal strength + -- 4. Updates LED display at regular intervals (refresh_time_ms) + -- 5. Maps audio amplitude to number of illuminated LEDs + -- 6. Creates bar graph visualization with automatic sensitivity scaling + -- + -- Leds Behavior: + -- - No audio: All LEDs off + -- - Low audio: Few LEDs illuminated (bottom of bar) + -- - High audio: Many LEDs illuminated (full bar) + -- - Overload: All LEDs illuminated (maximum indication) + END Behavioral; \ No newline at end of file diff --git a/LAB3/src/moving_average_filter.vhd b/LAB3/src/moving_average_filter.vhd index 9daa59a..881fa57 100644 --- a/LAB3/src/moving_average_filter.vhd +++ b/LAB3/src/moving_average_filter.vhd @@ -2,153 +2,199 @@ LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; USE ieee.numeric_std.ALL; +-- Entity: moving_average_filter +-- Purpose: Implements a finite impulse response (FIR) low-pass filter using moving average +-- Maintains separate circular buffers for left and right audio channels +-- Filter order is configurable as 2^(FILTER_ORDER_POWER) samples ENTITY moving_average_filter IS - GENERIC ( - -- Filter order expressed as 2^(FILTER_ORDER_POWER) - FILTER_ORDER_POWER : INTEGER := 5; + GENERIC ( + -- Filter order expressed as 2^(FILTER_ORDER_POWER) + -- Example: FILTER_ORDER_POWER = 5 means 32-sample moving average + FILTER_ORDER_POWER : INTEGER := 5; - TDATA_WIDTH : POSITIVE := 24 - ); - PORT ( - aclk : IN STD_LOGIC; - aresetn : IN STD_LOGIC; + TDATA_WIDTH : POSITIVE := 24 -- Width of audio data bus (24-bit audio samples) + ); + PORT ( + -- Clock and reset signals + aclk : IN STD_LOGIC; -- Main clock input + aresetn : IN STD_LOGIC; -- Active-low asynchronous reset - s_axis_tvalid : IN STD_LOGIC; - s_axis_tdata : IN STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); - s_axis_tlast : IN STD_LOGIC; - s_axis_tready : OUT STD_LOGIC; + -- AXI4-Stream Slave Interface (Audio Input) + s_axis_tvalid : IN STD_LOGIC; -- Input data valid signal + s_axis_tdata : IN STD_LOGIC_VECTOR(TDATA_WIDTH - 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 - m_axis_tvalid : OUT STD_LOGIC; - m_axis_tdata : OUT STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); - m_axis_tlast : OUT STD_LOGIC; - m_axis_tready : IN STD_LOGIC - ); + -- AXI4-Stream Master Interface (Audio Output) + m_axis_tvalid : OUT STD_LOGIC; -- Output data valid signal + m_axis_tdata : OUT STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); -- Filtered audio sample output + m_axis_tlast : OUT STD_LOGIC; -- Channel indicator passthrough + m_axis_tready : IN STD_LOGIC -- Downstream ready signal + ); END moving_average_filter; ARCHITECTURE Behavioral OF moving_average_filter IS - CONSTANT FILTER_ORDER : INTEGER := 2 ** FILTER_ORDER_POWER; + -- Calculate actual filter order from power-of-2 representation + CONSTANT FILTER_ORDER : INTEGER := 2 ** FILTER_ORDER_POWER; - TYPE sample_array IS ARRAY (0 TO FILTER_ORDER - 1) OF signed(TDATA_WIDTH - 1 DOWNTO 0); + -- Array type for storing audio samples in circular buffers + TYPE sample_array IS ARRAY (0 TO FILTER_ORDER - 1) OF signed(TDATA_WIDTH - 1 DOWNTO 0); - -- RX - SIGNAL samples_rx : sample_array := (OTHERS => (OTHERS => '0')); - SIGNAL sum_rx : signed(TDATA_WIDTH + FILTER_ORDER_POWER - 1 DOWNTO 0) := (OTHERS => '0'); - SIGNAL wr_ptr_rx : INTEGER RANGE 0 TO FILTER_ORDER - 1 := 0; + -- Right channel (RX) processing signals + SIGNAL samples_rx : sample_array := (OTHERS => (OTHERS => '0')); -- Circular buffer for right channel samples + SIGNAL sum_rx : signed(TDATA_WIDTH + FILTER_ORDER_POWER - 1 DOWNTO 0) := (OTHERS => '0'); -- Running sum of right channel samples + SIGNAL wr_ptr_rx : INTEGER RANGE 0 TO FILTER_ORDER - 1 := 0; -- Write pointer for right channel buffer - -- LX - SIGNAL samples_lx : sample_array := (OTHERS => (OTHERS => '0')); - SIGNAL sum_lx : signed(TDATA_WIDTH + FILTER_ORDER_POWER - 1 DOWNTO 0) := (OTHERS => '0'); - SIGNAL wr_ptr_lx : INTEGER RANGE 0 TO FILTER_ORDER - 1 := 0; + -- Left channel (LX) processing signals + SIGNAL samples_lx : sample_array := (OTHERS => (OTHERS => '0')); -- Circular buffer for left channel samples + SIGNAL sum_lx : signed(TDATA_WIDTH + FILTER_ORDER_POWER - 1 DOWNTO 0) := (OTHERS => '0'); -- Running sum of left channel samples + SIGNAL wr_ptr_lx : INTEGER RANGE 0 TO FILTER_ORDER - 1 := 0; -- Write pointer for left channel buffer - -- Trigger signal to indicate when to output data - SIGNAL trigger : STD_LOGIC := '0'; - - -- Output signals - SIGNAL s_axis_tready_int : STD_LOGIC := '0'; - SIGNAL s_axis_tlast_reg : STD_LOGIC := '0'; - SIGNAL m_axis_tvalid_int : STD_LOGIC := '0'; + -- Control and interface signals + SIGNAL trigger : STD_LOGIC := '0'; -- Trigger signal to indicate when to output filtered data + SIGNAL s_axis_tready_int : STD_LOGIC := '0'; -- Internal ready signal for input interface + SIGNAL s_axis_tlast_reg : STD_LOGIC := '0'; -- Registered version of tlast for output synchronization + SIGNAL m_axis_tvalid_int : STD_LOGIC := '0'; -- Internal valid signal for output interface BEGIN - -- Assigning the output signals - m_axis_tvalid <= m_axis_tvalid_int; - s_axis_tready <= s_axis_tready_int; + -- Connect internal signals to output ports + m_axis_tvalid <= m_axis_tvalid_int; + s_axis_tready <= s_axis_tready_int; - PROCESS (aclk) - BEGIN + -- Main processing logic - synchronous to clock + PROCESS (aclk) + BEGIN - IF rising_edge(aclk) THEN + IF rising_edge(aclk) THEN - IF aresetn = '0' THEN - samples_rx <= (OTHERS => (OTHERS => '0')); - samples_lx <= (OTHERS => (OTHERS => '0')); - sum_rx <= (OTHERS => '0'); - sum_lx <= (OTHERS => '0'); - wr_ptr_rx <= 0; - wr_ptr_lx <= 0; + IF aresetn = '0' THEN + -- Reset all filter state and control signals + samples_rx <= (OTHERS => (OTHERS => '0')); -- Clear right channel sample buffer + samples_lx <= (OTHERS => (OTHERS => '0')); -- Clear left channel sample buffer + sum_rx <= (OTHERS => '0'); -- Clear right channel running sum + sum_lx <= (OTHERS => '0'); -- Clear left channel running sum + wr_ptr_rx <= 0; -- Reset right channel write pointer + wr_ptr_lx <= 0; -- Reset left channel write pointer - s_axis_tlast_reg <= '0'; - s_axis_tready_int <= '0'; - m_axis_tvalid_int <= '0'; - m_axis_tlast <= '0'; + -- Reset AXI4-Stream interface signals + s_axis_tlast_reg <= '0'; -- Clear registered tlast + s_axis_tready_int <= '0'; -- Not ready during reset + m_axis_tvalid_int <= '0'; -- No valid output during reset + m_axis_tlast <= '0'; -- Clear output tlast - ELSE - -- Clear valid flag when master interface is ready - IF m_axis_tready = '1' THEN - m_axis_tvalid_int <= '0'; - END IF; + ELSE + -- Normal operation - -- Data output logic - IF trigger = '1' AND (m_axis_tvalid_int = '0' OR m_axis_tready = '1') THEN - IF s_axis_tlast_reg = '1' THEN - m_axis_tdata <= STD_LOGIC_VECTOR( - resize( - shift_right( - sum_rx, - FILTER_ORDER_POWER - ), - m_axis_tdata'length - ) - ); + -- Output handshake management: + -- Clear valid flag when downstream accepts data + IF m_axis_tready = '1' THEN + m_axis_tvalid_int <= '0'; + END IF; - ELSE - m_axis_tdata <= STD_LOGIC_VECTOR( - resize( - shift_right( - sum_lx, - FILTER_ORDER_POWER - ), - m_axis_tdata'length - ) - ); + -- Data output logic: + -- Output filtered data when trigger is set AND output interface is available + IF trigger = '1' AND (m_axis_tvalid_int = '0' OR m_axis_tready = '1') THEN + + -- Select which channel's filtered data to output based on registered tlast + IF s_axis_tlast_reg = '1' THEN + -- Right channel: Output averaged right channel data + -- Divide sum by filter order using right shift (efficient division by power of 2) + m_axis_tdata <= STD_LOGIC_VECTOR( + resize( + shift_right( + sum_rx, -- Right channel running sum + FILTER_ORDER_POWER -- Divide by 2^FILTER_ORDER_POWER + ), + m_axis_tdata'length -- Resize to output width + ) + ); - END IF; - - m_axis_tlast <= s_axis_tlast_reg; + ELSE + -- Left channel: Output averaged left channel data + -- Divide sum by filter order using right shift (efficient division by power of 2) + m_axis_tdata <= STD_LOGIC_VECTOR( + resize( + shift_right( + sum_lx, -- Left channel running sum + FILTER_ORDER_POWER -- Divide by 2^FILTER_ORDER_POWER + ), + m_axis_tdata'length -- Resize to output width + ) + ); - m_axis_tvalid_int <= '1'; - trigger <= '0'; - END IF; + END IF; + + -- Set output control signals + m_axis_tlast <= s_axis_tlast_reg; -- Pass through the registered channel indicator + m_axis_tvalid_int <= '1'; -- Mark output as valid + trigger <= '0'; -- Clear trigger - data has been output - -- Data input logic - IF s_axis_tvalid = '1' AND s_axis_tready_int = '1' THEN - IF s_axis_tlast = '1' THEN - -- Right channel - -- Circular buffer overwrite oldest saple with the new one from next clk cycle - samples_rx(wr_ptr_rx) <= signed(s_axis_tdata); + END IF; - -- Update the write pointer - wr_ptr_rx <= (wr_ptr_rx + 1) MOD FILTER_ORDER; + -- Data input logic: + -- Process new input samples when both valid and ready are asserted + IF s_axis_tvalid = '1' AND s_axis_tready_int = '1' THEN + + -- Channel identification and processing based on tlast signal + IF s_axis_tlast = '1' THEN + -- Right channel processing (tlast = '1') + + -- Store new sample in circular buffer, overwriting oldest sample + samples_rx(wr_ptr_rx) <= signed(s_axis_tdata); - -- Update the sum_rx removing the oldest sample and adding the new one - sum_rx <= sum_rx - samples_rx(wr_ptr_rx) + signed(s_axis_tdata); + -- Update write pointer with wraparound (circular buffer behavior) + wr_ptr_rx <= (wr_ptr_rx + 1) MOD FILTER_ORDER; - s_axis_tlast_reg <= s_axis_tlast; - ELSE - -- Left channel - -- Circular buffer overwrite oldest saple with the new one from next clk cycle - samples_lx(wr_ptr_lx) <= signed(s_axis_tdata); + -- Update running sum using sliding window technique: + -- Remove the oldest sample (about to be overwritten) and add the new sample + -- This maintains the sum of the most recent FILTER_ORDER samples + sum_rx <= sum_rx - samples_rx(wr_ptr_rx) + signed(s_axis_tdata); - -- Update the write pointer - wr_ptr_lx <= (wr_ptr_lx + 1) MOD FILTER_ORDER; + -- Register tlast for output synchronization + s_axis_tlast_reg <= s_axis_tlast; - -- Update the sum_rx removing the oldest sample and adding the new one - sum_lx <= sum_lx - samples_lx(wr_ptr_lx) + signed(s_axis_tdata); + ELSE + -- Left channel processing (tlast = '0') + + -- Store new sample in circular buffer, overwriting oldest sample + samples_lx(wr_ptr_lx) <= signed(s_axis_tdata); - s_axis_tlast_reg <= s_axis_tlast; - END IF; + -- Update write pointer with wraparound (circular buffer behavior) + wr_ptr_lx <= (wr_ptr_lx + 1) MOD FILTER_ORDER; - trigger <= '1'; + -- Update running sum using sliding window technique: + -- Remove the oldest sample (about to be overwritten) and add the new sample + -- This maintains the sum of the most recent FILTER_ORDER samples + sum_lx <= sum_lx - samples_lx(wr_ptr_lx) + signed(s_axis_tdata); - END IF; + -- Register tlast for output synchronization + s_axis_tlast_reg <= s_axis_tlast; - s_axis_tready_int <= m_axis_tready OR NOT m_axis_tvalid_int; + END IF; - END IF; + -- Set trigger to indicate that new filtered data is ready for output + trigger <= '1'; - END IF; + END IF; - END PROCESS; + -- Input ready logic: + -- Ready to accept new input when downstream is ready OR no valid output pending + -- This implements proper AXI4-Stream backpressure handling + s_axis_tready_int <= m_axis_tready OR NOT m_axis_tvalid_int; + + END IF; + + END IF; + + END PROCESS; + + -- Filter Operation Summary: + -- 1. Maintains separate circular buffers for left and right audio channels + -- 2. Uses running sum technique for efficient moving average calculation + -- 3. Each new sample: removes oldest from sum, adds newest to sum + -- 4. Output = sum / FILTER_ORDER (using efficient bit shift division) + -- 5. Provides low-pass filtering with configurable filter order END Behavioral; \ No newline at end of file diff --git a/LAB3/src/moving_average_filter_en.vhd b/LAB3/src/moving_average_filter_en.vhd index 0d3f037..3386a56 100644 --- a/LAB3/src/moving_average_filter_en.vhd +++ b/LAB3/src/moving_average_filter_en.vhd @@ -2,135 +2,167 @@ LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; USE ieee.numeric_std.ALL; +-- Entity: moving_average_filter_en +-- Purpose: Switchable audio filter that can be enabled or disabled at runtime +-- When enabled: applies moving average low-pass filtering +-- When disabled: passes audio through unchanged (all-pass filter) +-- This allows real-time comparison between filtered and unfiltered audio ENTITY moving_average_filter_en IS - GENERIC ( - -- Filter order expressed as 2^(FILTER_ORDER_POWER) - FILTER_ORDER_POWER : INTEGER := 5; + GENERIC ( + -- Filter order expressed as 2^(FILTER_ORDER_POWER) + -- Example: FILTER_ORDER_POWER = 5 means filter order = 32 samples + FILTER_ORDER_POWER : INTEGER := 5; - TDATA_WIDTH : POSITIVE := 24 - ); - PORT ( - aclk : IN STD_LOGIC; - aresetn : IN STD_LOGIC; + TDATA_WIDTH : POSITIVE := 24 -- Width of audio data bus (24-bit audio samples) + ); + PORT ( + -- Clock and reset signals + aclk : IN STD_LOGIC; -- Main clock input + aresetn : IN STD_LOGIC; -- Active-low asynchronous reset - s_axis_tvalid : IN STD_LOGIC; - s_axis_tdata : IN STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); - s_axis_tlast : IN STD_LOGIC; - s_axis_tready : OUT STD_LOGIC; + -- AXI4-Stream Slave Interface (Audio Input) + s_axis_tvalid : IN STD_LOGIC; -- Input data valid signal + s_axis_tdata : IN STD_LOGIC_VECTOR(TDATA_WIDTH - 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 - m_axis_tvalid : OUT STD_LOGIC; - m_axis_tdata : OUT STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); - m_axis_tlast : OUT STD_LOGIC; - m_axis_tready : IN STD_LOGIC; + -- AXI4-Stream Master Interface (Audio Output) + m_axis_tvalid : OUT STD_LOGIC; -- Output data valid signal + m_axis_tdata : OUT STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); -- Audio sample output (filtered or unfiltered) + m_axis_tlast : OUT STD_LOGIC; -- Channel indicator passthrough + m_axis_tready : IN STD_LOGIC; -- Downstream ready signal - enable_filter : IN STD_LOGIC - ); + -- Filter control input + enable_filter : IN STD_LOGIC -- Filter enable control (1=moving average filter, 0=all-pass filter) + ); END moving_average_filter_en; ARCHITECTURE Behavioral OF moving_average_filter_en IS - -- Component declarations - COMPONENT all_pass_filter IS - GENERIC ( - TDATA_WIDTH : POSITIVE := 24 - ); - PORT ( - aclk : IN STD_LOGIC; - aresetn : IN STD_LOGIC; + -- Component declaration for all-pass filter + -- This filter passes audio unchanged but maintains the same latency as the moving average filter + COMPONENT all_pass_filter IS + GENERIC ( + TDATA_WIDTH : POSITIVE := 24 + ); + PORT ( + aclk : IN STD_LOGIC; + aresetn : IN STD_LOGIC; - s_axis_tvalid : IN STD_LOGIC; - s_axis_tdata : IN STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); - s_axis_tlast : IN STD_LOGIC; - s_axis_tready : OUT STD_LOGIC; + s_axis_tvalid : IN STD_LOGIC; + s_axis_tdata : IN STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); + s_axis_tlast : IN STD_LOGIC; + s_axis_tready : OUT STD_LOGIC; - m_axis_tvalid : OUT STD_LOGIC; - m_axis_tdata : OUT STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); - m_axis_tlast : OUT STD_LOGIC; - m_axis_tready : IN STD_LOGIC - ); - END COMPONENT; + m_axis_tvalid : OUT STD_LOGIC; + m_axis_tdata : OUT STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); + m_axis_tlast : OUT STD_LOGIC; + m_axis_tready : IN STD_LOGIC + ); + END COMPONENT; - COMPONENT moving_average_filter IS - GENERIC ( - FILTER_ORDER_POWER : INTEGER := 5; - TDATA_WIDTH : POSITIVE := 24 - ); - PORT ( - aclk : IN STD_LOGIC; - aresetn : IN STD_LOGIC; + -- Component declaration for moving average filter + -- This filter applies low-pass filtering by averaging multiple audio samples + COMPONENT moving_average_filter IS + GENERIC ( + FILTER_ORDER_POWER : INTEGER := 5; + TDATA_WIDTH : POSITIVE := 24 + ); + PORT ( + aclk : IN STD_LOGIC; + aresetn : IN STD_LOGIC; - s_axis_tvalid : IN STD_LOGIC; - s_axis_tdata : IN STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); - s_axis_tlast : IN STD_LOGIC; - s_axis_tready : OUT STD_LOGIC; - - m_axis_tvalid : OUT STD_LOGIC; - m_axis_tdata : OUT STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); - m_axis_tlast : OUT STD_LOGIC; - m_axis_tready : IN STD_LOGIC - ); - END COMPONENT; + s_axis_tvalid : IN STD_LOGIC; + s_axis_tdata : IN STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); + s_axis_tlast : IN STD_LOGIC; + s_axis_tready : OUT STD_LOGIC; + + m_axis_tvalid : OUT STD_LOGIC; + m_axis_tdata : OUT STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); + m_axis_tlast : OUT STD_LOGIC; + m_axis_tready : IN STD_LOGIC + ); + END COMPONENT; - -- Internal signals for the all-pass filter - SIGNAL all_pass_m_tvalid : STD_LOGIC; - SIGNAL all_pass_m_tdata : STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); - SIGNAL all_pass_m_tlast : STD_LOGIC; - SIGNAL all_pass_s_tready : STD_LOGIC; + -- Internal signals for the all-pass filter path + -- These signals carry data when filtering is disabled + SIGNAL all_pass_m_tvalid : STD_LOGIC; -- All-pass filter output valid + SIGNAL all_pass_m_tdata : STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); -- All-pass filter output data + SIGNAL all_pass_m_tlast : STD_LOGIC; -- All-pass filter output tlast + SIGNAL all_pass_s_tready : STD_LOGIC; -- All-pass filter input ready - -- Internal signals for the moving average filter - SIGNAL moving_avg_m_tvalid : STD_LOGIC; - SIGNAL moving_avg_m_tdata : STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); - SIGNAL moving_avg_m_tlast : STD_LOGIC; - SIGNAL moving_avg_s_tready : STD_LOGIC; + -- Internal signals for the moving average filter path + -- These signals carry data when filtering is enabled + SIGNAL moving_avg_m_tvalid : STD_LOGIC; -- Moving average filter output valid + SIGNAL moving_avg_m_tdata : STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); -- Moving average filter output data + SIGNAL moving_avg_m_tlast : STD_LOGIC; -- Moving average filter output tlast + SIGNAL moving_avg_s_tready : STD_LOGIC; -- Moving average filter input ready BEGIN - -- Instantiate the all-pass filter - all_pass_inst : all_pass_filter - GENERIC MAP( - TDATA_WIDTH => TDATA_WIDTH - ) - PORT MAP( - aclk => aclk, - aresetn => aresetn, + -- Instantiate the all-pass filter (bypass path) + -- This provides unfiltered audio with matched latency for seamless switching + all_pass_inst : all_pass_filter + GENERIC MAP( + TDATA_WIDTH => TDATA_WIDTH + ) + PORT MAP( + aclk => aclk, + aresetn => aresetn, - s_axis_tvalid => s_axis_tvalid, - s_axis_tdata => s_axis_tdata, - s_axis_tlast => s_axis_tlast, - s_axis_tready => all_pass_s_tready, + -- Connect input directly to all-pass filter + s_axis_tvalid => s_axis_tvalid, + s_axis_tdata => s_axis_tdata, + s_axis_tlast => s_axis_tlast, + s_axis_tready => all_pass_s_tready, - m_axis_tvalid => all_pass_m_tvalid, - m_axis_tdata => all_pass_m_tdata, - m_axis_tlast => all_pass_m_tlast, - m_axis_tready => m_axis_tready - ); + -- All-pass filter outputs (used when enable_filter = '0') + m_axis_tvalid => all_pass_m_tvalid, + m_axis_tdata => all_pass_m_tdata, + m_axis_tlast => all_pass_m_tlast, + m_axis_tready => m_axis_tready + ); - -- Instantiate the moving average filter - moving_avg_inst : moving_average_filter - GENERIC MAP( - FILTER_ORDER_POWER => FILTER_ORDER_POWER, - TDATA_WIDTH => TDATA_WIDTH - ) - PORT MAP( - aclk => aclk, - aresetn => aresetn, + -- Instantiate the moving average filter (filtering path) + -- This provides low-pass filtered audio for noise reduction + moving_avg_inst : moving_average_filter + GENERIC MAP( + FILTER_ORDER_POWER => FILTER_ORDER_POWER, + TDATA_WIDTH => TDATA_WIDTH + ) + PORT MAP( + aclk => aclk, + aresetn => aresetn, - s_axis_tvalid => s_axis_tvalid, - s_axis_tdata => s_axis_tdata, - s_axis_tlast => s_axis_tlast, - s_axis_tready => moving_avg_s_tready, + -- Connect input directly to moving average filter + s_axis_tvalid => s_axis_tvalid, + s_axis_tdata => s_axis_tdata, + s_axis_tlast => s_axis_tlast, + s_axis_tready => moving_avg_s_tready, - m_axis_tvalid => moving_avg_m_tvalid, - m_axis_tdata => moving_avg_m_tdata, - m_axis_tlast => moving_avg_m_tlast, - m_axis_tready => m_axis_tready - ); + -- Moving average filter outputs (used when enable_filter = '1') + m_axis_tvalid => moving_avg_m_tvalid, + m_axis_tdata => moving_avg_m_tdata, + m_axis_tlast => moving_avg_m_tlast, + m_axis_tready => m_axis_tready + ); - -- Main AXIS assignments based on enable_filter - s_axis_tready <= all_pass_s_tready WHEN enable_filter = '0' ELSE moving_avg_s_tready; - - m_axis_tvalid <= all_pass_m_tvalid WHEN enable_filter = '0' ELSE moving_avg_m_tvalid; - m_axis_tdata <= all_pass_m_tdata WHEN enable_filter = '0' ELSE moving_avg_m_tdata; - m_axis_tlast <= all_pass_m_tlast WHEN enable_filter = '0' ELSE moving_avg_m_tlast; + -- Output multiplexer: Select between filtered and unfiltered audio paths + -- This switching is controlled by the enable_filter signal + + -- Input ready selection: Route backpressure to the active filter + s_axis_tready <= all_pass_s_tready WHEN enable_filter = '0' ELSE moving_avg_s_tready; + + -- Output data selection: Route output from the active filter + m_axis_tvalid <= all_pass_m_tvalid WHEN enable_filter = '0' ELSE moving_avg_m_tvalid; + m_axis_tdata <= all_pass_m_tdata WHEN enable_filter = '0' ELSE moving_avg_m_tdata; + m_axis_tlast <= all_pass_m_tlast WHEN enable_filter = '0' ELSE moving_avg_m_tlast; + + -- Filter Path Selection Logic: + -- enable_filter = '0': All-pass path (unfiltered audio with matched latency) + -- enable_filter = '1': Moving average path (low-pass filtered audio) + -- + -- Both filters run continuously but only the selected path is routed to output + -- This allows for glitch-free switching between filtered and unfiltered audio END Behavioral; \ No newline at end of file diff --git a/LAB3/src/mute_controller.vhd b/LAB3/src/mute_controller.vhd index 217c66a..32e5fc8 100644 --- a/LAB3/src/mute_controller.vhd +++ b/LAB3/src/mute_controller.vhd @@ -2,70 +2,95 @@ LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; USE IEEE.NUMERIC_STD.ALL; +-- Entity: mute_controller +-- Purpose: Controls audio muting by replacing audio samples with zeros when mute is active +-- Implements AXI4-Stream interface for seamless integration in audio processing chain ENTITY mute_controller IS - GENERIC ( - TDATA_WIDTH : POSITIVE := 24 - ); - PORT ( - aclk : IN STD_LOGIC; - aresetn : IN STD_LOGIC; + GENERIC ( + TDATA_WIDTH : POSITIVE := 24 -- Width of audio data bus (24-bit audio samples) + ); + PORT ( + -- Clock and reset signals + aclk : IN STD_LOGIC; -- Main clock input + aresetn : IN STD_LOGIC; -- Active-low asynchronous reset - s_axis_tvalid : IN STD_LOGIC; - s_axis_tdata : IN STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); - s_axis_tlast : IN STD_LOGIC; - s_axis_tready : OUT STD_LOGIC; + -- AXI4-Stream Slave Interface (Audio Input) + s_axis_tvalid : IN STD_LOGIC; -- Input data valid signal + s_axis_tdata : IN STD_LOGIC_VECTOR(TDATA_WIDTH - 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 - m_axis_tvalid : OUT STD_LOGIC; - m_axis_tdata : OUT STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); - m_axis_tlast : OUT STD_LOGIC; - m_axis_tready : IN STD_LOGIC; + -- AXI4-Stream Master Interface (Audio Output) + m_axis_tvalid : OUT STD_LOGIC; -- Output data valid signal + m_axis_tdata : OUT STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); -- Audio sample output (muted or passed through) + m_axis_tlast : OUT STD_LOGIC; -- Channel indicator passthrough + m_axis_tready : IN STD_LOGIC; -- Downstream ready signal - mute : IN STD_LOGIC - ); + -- Control input + mute : IN STD_LOGIC -- Mute control (1=mute audio, 0=pass audio through) + ); END mute_controller; ARCHITECTURE Behavioral OF mute_controller IS - SIGNAL m_axis_tvalid_int : STD_LOGIC; + -- Internal signal for output valid control + SIGNAL m_axis_tvalid_int : STD_LOGIC; BEGIN - -- Assigning the output signals - m_axis_tvalid <= m_axis_tvalid_int; - s_axis_tready <= (m_axis_tready OR NOT m_axis_tvalid_int) AND aresetn; + -- Connect internal signals to output ports + m_axis_tvalid <= m_axis_tvalid_int; + + -- Input ready logic: Ready when downstream is ready OR no valid data pending, AND not in reset + -- This implements proper AXI4-Stream backpressure handling + s_axis_tready <= (m_axis_tready OR NOT m_axis_tvalid_int) AND aresetn; - PROCESS (aclk) - BEGIN + -- Main mute control process + -- Handles AXI4-Stream protocol and mute functionality + PROCESS (aclk) + BEGIN - IF rising_edge(aclk) THEN + IF rising_edge(aclk) THEN - IF aresetn = '0' THEN - m_axis_tvalid_int <= '0'; - m_axis_tlast <= '0'; + IF aresetn = '0' THEN + -- Reset state: Clear all output control signals + m_axis_tvalid_int <= '0'; -- No valid output data during reset + m_axis_tlast <= '0'; -- Clear channel indicator - ELSE - -- Clear valid flag when master interface is ready - IF m_axis_tready = '1' THEN - m_axis_tvalid_int <= '0'; - END IF; + ELSE + -- Normal operation - -- Handle the data flow - IF s_axis_tvalid = '1' AND m_axis_tready = '1' THEN - IF mute = '1' THEN - m_axis_tdata <= (OTHERS => '0'); - ELSE - m_axis_tdata <= s_axis_tdata; - END IF; + -- Output handshake management: + -- Clear valid flag when downstream accepts data + -- This allows new data to be output on the next cycle + IF m_axis_tready = '1' THEN + m_axis_tvalid_int <= '0'; + END IF; - m_axis_tvalid_int <= '1'; - m_axis_tlast <= s_axis_tlast; + -- Data processing: Handle mute control when both input and output are ready + -- This ensures proper AXI4-Stream handshaking + IF s_axis_tvalid = '1' AND m_axis_tready = '1' THEN + -- Mute control logic: + IF mute = '1' THEN + -- Mute active: Replace audio data with silence (all zeros) + -- This effectively removes all audio content while maintaining data flow + m_axis_tdata <= (OTHERS => '0'); + ELSE + -- Mute inactive: Pass audio data through unchanged + -- Normal audio processing mode + m_axis_tdata <= s_axis_tdata; + END IF; - END IF; + -- Set output control signals + m_axis_tvalid_int <= '1'; -- Mark output data as valid + m_axis_tlast <= s_axis_tlast; -- Pass through channel indicator unchanged - END IF; + END IF; - END IF; - - END PROCESS; + END IF; + + END IF; + + END PROCESS; END Behavioral; \ No newline at end of file diff --git a/LAB3/src/volume_controller.vhd b/LAB3/src/volume_controller.vhd index f983367..b251243 100644 --- a/LAB3/src/volume_controller.vhd +++ b/LAB3/src/volume_controller.vhd @@ -2,137 +2,169 @@ LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; USE IEEE.NUMERIC_STD.ALL; +-- Entity: volume_controller +-- Purpose: Controls audio volume by scaling audio samples according to volume control input +-- Implements a two-stage processing pipeline: multiplication followed by saturation +-- This approach prevents overflow and distortion in the audio signal ENTITY volume_controller IS - GENERIC ( - TDATA_WIDTH : POSITIVE := 24; - VOLUME_WIDTH : POSITIVE := 10; - VOLUME_STEP_2 : POSITIVE := 6; -- i.e., volume_values_per_step = 2**VOLUME_STEP_2 - HIGHER_BOUND : INTEGER := 2 ** 23 - 1; -- Inclusive - LOWER_BOUND : INTEGER := - 2 ** 23 -- Inclusive - ); - PORT ( - aclk : IN STD_LOGIC; - aresetn : IN STD_LOGIC; + GENERIC ( + TDATA_WIDTH : POSITIVE := 24; -- Width of audio data bus (24-bit audio samples) + VOLUME_WIDTH : POSITIVE := 10; -- Width of volume control input (10-bit = 0-1023 range) + VOLUME_STEP_2 : POSITIVE := 6; -- Log2 of volume values per step (2^6 = 64 values per step) + HIGHER_BOUND : INTEGER := 2 ** 23 - 1; -- Maximum positive value for saturation (inclusive) + LOWER_BOUND : INTEGER := - 2 ** 23 -- Maximum negative value for saturation (inclusive) + ); + PORT ( + -- Clock and reset signals + aclk : IN STD_LOGIC; -- Main clock input + aresetn : IN STD_LOGIC; -- Active-low asynchronous reset - s_axis_tvalid : IN STD_LOGIC; - s_axis_tdata : IN STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); - s_axis_tlast : IN STD_LOGIC; - s_axis_tready : OUT STD_LOGIC; + -- AXI4-Stream Slave Interface (Audio Input) + s_axis_tvalid : IN STD_LOGIC; -- Input data valid signal + s_axis_tdata : IN STD_LOGIC_VECTOR(TDATA_WIDTH - 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 - m_axis_tvalid : OUT STD_LOGIC; - m_axis_tdata : OUT STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); - m_axis_tlast : OUT STD_LOGIC; - m_axis_tready : IN STD_LOGIC; + -- AXI4-Stream Master Interface (Audio Output) + m_axis_tvalid : OUT STD_LOGIC; -- Output data valid signal + m_axis_tdata : OUT STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); -- Audio sample output (volume adjusted) + m_axis_tlast : OUT STD_LOGIC; -- Channel indicator passthrough + m_axis_tready : IN STD_LOGIC; -- Downstream ready signal - volume : IN STD_LOGIC_VECTOR(VOLUME_WIDTH - 1 DOWNTO 0) - ); + -- Volume control input + volume : IN STD_LOGIC_VECTOR(VOLUME_WIDTH - 1 DOWNTO 0) -- Volume level (0=minimum, 1023=maximum) + ); END volume_controller; ARCHITECTURE Behavioral OF volume_controller IS - -- Component declarations - COMPONENT volume_multiplier IS - GENERIC ( - TDATA_WIDTH : POSITIVE := 24; - VOLUME_WIDTH : POSITIVE := 10; - VOLUME_STEP_2 : POSITIVE := 6 -- i.e., volume_values_per_step = 2**VOLUME_STEP_2 - ); - PORT ( - aclk : IN STD_LOGIC; - aresetn : IN STD_LOGIC; + -- Component declaration for volume multiplier + -- First stage: multiplies audio samples by volume scaling factor + -- Output has wider bit width to accommodate multiplication results + COMPONENT volume_multiplier IS + GENERIC ( + TDATA_WIDTH : POSITIVE := 24; -- Input audio data width + VOLUME_WIDTH : POSITIVE := 10; -- Volume control width + VOLUME_STEP_2 : POSITIVE := 6 -- Step size for volume control + ); + PORT ( + aclk : IN STD_LOGIC; + aresetn : IN STD_LOGIC; - s_axis_tvalid : IN STD_LOGIC; - s_axis_tdata : IN STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); - s_axis_tlast : IN STD_LOGIC; - s_axis_tready : OUT STD_LOGIC; + -- Input AXI4-Stream interface + s_axis_tvalid : IN STD_LOGIC; + s_axis_tdata : IN STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); + s_axis_tlast : IN STD_LOGIC; + s_axis_tready : OUT STD_LOGIC; - m_axis_tvalid : OUT STD_LOGIC; - m_axis_tdata : OUT STD_LOGIC_VECTOR(TDATA_WIDTH - 1 + 2 ** (VOLUME_WIDTH - VOLUME_STEP_2 - 1) DOWNTO 0); - m_axis_tlast : OUT STD_LOGIC; - m_axis_tready : IN STD_LOGIC; + -- Output AXI4-Stream interface (wider data width due to multiplication) + m_axis_tvalid : OUT STD_LOGIC; + m_axis_tdata : OUT STD_LOGIC_VECTOR(TDATA_WIDTH - 1 + 2 ** (VOLUME_WIDTH - VOLUME_STEP_2 - 1) DOWNTO 0); + m_axis_tlast : OUT STD_LOGIC; + m_axis_tready : IN STD_LOGIC; - volume : IN STD_LOGIC_VECTOR(VOLUME_WIDTH - 1 DOWNTO 0) - ); - END COMPONENT; + volume : IN STD_LOGIC_VECTOR(VOLUME_WIDTH - 1 DOWNTO 0) + ); + END COMPONENT; - COMPONENT volume_saturator IS - GENERIC ( - TDATA_WIDTH : POSITIVE := 24; - VOLUME_WIDTH : POSITIVE := 10; - VOLUME_STEP_2 : POSITIVE := 6; -- i.e., number_of_steps = 2**VOLUME_STEP_2 - HIGHER_BOUND : INTEGER := 2 ** 15 - 1; -- Inclusive - LOWER_BOUND : INTEGER := - 2 ** 15 -- Inclusive - ); - PORT ( - aclk : IN STD_LOGIC; - aresetn : IN STD_LOGIC; + -- Component declaration for volume saturator + -- Second stage: clips multiplication results to prevent overflow and distortion + -- Reduces bit width back to original audio format + COMPONENT volume_saturator IS + GENERIC ( + TDATA_WIDTH : POSITIVE := 24; -- Final audio data width + VOLUME_WIDTH : POSITIVE := 10; -- Volume control width + VOLUME_STEP_2 : POSITIVE := 6; -- Step size for volume control + HIGHER_BOUND : INTEGER := 2 ** 15 - 1; -- Upper saturation limit (inclusive) + LOWER_BOUND : INTEGER := - 2 ** 15 -- Lower saturation limit (inclusive) + ); + PORT ( + aclk : IN STD_LOGIC; + aresetn : IN STD_LOGIC; - s_axis_tvalid : IN STD_LOGIC; - s_axis_tdata : IN STD_LOGIC_VECTOR(TDATA_WIDTH - 1 + 2 ** (VOLUME_WIDTH - VOLUME_STEP_2 - 1) DOWNTO 0); - s_axis_tlast : IN STD_LOGIC; - s_axis_tready : OUT STD_LOGIC; + -- Input AXI4-Stream interface (wide data from multiplier) + s_axis_tvalid : IN STD_LOGIC; + s_axis_tdata : IN STD_LOGIC_VECTOR(TDATA_WIDTH - 1 + 2 ** (VOLUME_WIDTH - VOLUME_STEP_2 - 1) DOWNTO 0); + s_axis_tlast : IN STD_LOGIC; + s_axis_tready : OUT STD_LOGIC; - m_axis_tvalid : OUT STD_LOGIC; - m_axis_tdata : OUT STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); - m_axis_tlast : OUT STD_LOGIC; - m_axis_tready : IN STD_LOGIC - ); - END COMPONENT; + -- Output AXI4-Stream interface (original audio data width) + m_axis_tvalid : OUT STD_LOGIC; + m_axis_tdata : OUT STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); + m_axis_tlast : OUT STD_LOGIC; + m_axis_tready : IN STD_LOGIC + ); + END COMPONENT; - -- Internal AXIS signals - SIGNAL int_axis_tvalid : STD_LOGIC; - SIGNAL int_axis_tready : STD_LOGIC; - SIGNAL int_axis_tdata : STD_LOGIC_VECTOR(TDATA_WIDTH - 1 + 2 ** (VOLUME_WIDTH - VOLUME_STEP_2 - 1) DOWNTO 0); - SIGNAL int_axis_tlast : STD_LOGIC; + -- Internal AXI4-Stream signals between multiplier and saturator + -- These signals carry the wide multiplication results before saturation + SIGNAL int_axis_tvalid : STD_LOGIC; -- Valid signal between stages + SIGNAL int_axis_tready : STD_LOGIC; -- Ready signal between stages + SIGNAL int_axis_tdata : STD_LOGIC_VECTOR(TDATA_WIDTH - 1 + 2 ** (VOLUME_WIDTH - VOLUME_STEP_2 - 1) DOWNTO 0); -- Wide data between stages + SIGNAL int_axis_tlast : STD_LOGIC; -- Channel indicator between stages BEGIN - -- Instantiate volume_multiplier - volume_multiplier_inst : volume_multiplier - GENERIC MAP( - TDATA_WIDTH => TDATA_WIDTH, - VOLUME_WIDTH => VOLUME_WIDTH, - VOLUME_STEP_2 => VOLUME_STEP_2 - ) - PORT MAP( - aclk => aclk, - aresetn => aresetn, + -- Instantiate volume_multiplier (First Stage) + -- Multiplies incoming audio samples by volume scaling factor + -- Output has extended bit width to prevent loss of precision + volume_multiplier_inst : volume_multiplier + GENERIC MAP( + TDATA_WIDTH => TDATA_WIDTH, -- Input audio sample width + VOLUME_WIDTH => VOLUME_WIDTH, -- Volume control resolution + VOLUME_STEP_2 => VOLUME_STEP_2 -- Volume step size + ) + PORT MAP( + aclk => aclk, + aresetn => aresetn, - s_axis_tvalid => s_axis_tvalid, - s_axis_tdata => s_axis_tdata, - s_axis_tlast => s_axis_tlast, - s_axis_tready => s_axis_tready, + -- Connect to external input interface + s_axis_tvalid => s_axis_tvalid, + s_axis_tdata => s_axis_tdata, + s_axis_tlast => s_axis_tlast, + s_axis_tready => s_axis_tready, - m_axis_tvalid => int_axis_tvalid, - m_axis_tdata => int_axis_tdata, - m_axis_tlast => int_axis_tlast, - m_axis_tready => int_axis_tready, + -- Connect to internal interface (wide data) + m_axis_tvalid => int_axis_tvalid, + m_axis_tdata => int_axis_tdata, + m_axis_tlast => int_axis_tlast, + m_axis_tready => int_axis_tready, - volume => volume - ); + volume => volume + ); - -- Instantiate volume_saturator - volume_saturator_inst : volume_saturator - GENERIC MAP( - TDATA_WIDTH => TDATA_WIDTH, - VOLUME_WIDTH => VOLUME_WIDTH, - VOLUME_STEP_2 => VOLUME_STEP_2, - HIGHER_BOUND => HIGHER_BOUND, - LOWER_BOUND => LOWER_BOUND - ) - PORT MAP( - aclk => aclk, - aresetn => aresetn, + -- Instantiate volume_saturator (Second Stage) + -- Clips multiplication results to prevent overflow and distortion + -- Reduces bit width back to original audio format for output + volume_saturator_inst : volume_saturator + GENERIC MAP( + TDATA_WIDTH => TDATA_WIDTH, -- Final audio sample width + VOLUME_WIDTH => VOLUME_WIDTH, -- Volume control resolution + VOLUME_STEP_2 => VOLUME_STEP_2, -- Volume step size + HIGHER_BOUND => HIGHER_BOUND, -- Upper saturation limit + LOWER_BOUND => LOWER_BOUND -- Lower saturation limit + ) + PORT MAP( + aclk => aclk, + aresetn => aresetn, - s_axis_tvalid => int_axis_tvalid, - s_axis_tdata => int_axis_tdata, - s_axis_tlast => int_axis_tlast, - s_axis_tready => int_axis_tready, + -- Connect to internal interface (wide data from multiplier) + s_axis_tvalid => int_axis_tvalid, + s_axis_tdata => int_axis_tdata, + s_axis_tlast => int_axis_tlast, + s_axis_tready => int_axis_tready, - m_axis_tvalid => m_axis_tvalid, - m_axis_tdata => m_axis_tdata, - m_axis_tlast => m_axis_tlast, - m_axis_tready => m_axis_tready - ); + -- Connect to external output interface + m_axis_tvalid => m_axis_tvalid, + m_axis_tdata => m_axis_tdata, + m_axis_tlast => m_axis_tlast, + m_axis_tready => m_axis_tready + ); + + -- Pipeline Operation: + -- 1. Audio samples enter volume_multiplier with original bit width + -- 2. Multiplier scales samples by volume factor, output has extended bit width + -- 3. Saturator clips results to prevent overflow, reduces to original bit width + -- 4. Final audio samples has adjusted volume and bit width, ready for downstream processing END Behavioral; \ No newline at end of file diff --git a/LAB3/src/volume_multiplier.vhd b/LAB3/src/volume_multiplier.vhd index dd4f041..19106fb 100644 --- a/LAB3/src/volume_multiplier.vhd +++ b/LAB3/src/volume_multiplier.vhd @@ -2,107 +2,168 @@ LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; USE IEEE.NUMERIC_STD.ALL; +-- Entity: volume_multiplier +-- Purpose: First stage of volume control pipeline - multiplies audio samples by volume scaling factor +-- Uses bit-shifting multiplication for efficient hardware implementation +-- Implements exponential volume scaling for natural-feeling volume control ENTITY volume_multiplier IS - GENERIC ( - TDATA_WIDTH : POSITIVE := 24; - VOLUME_WIDTH : POSITIVE := 10; - VOLUME_STEP_2 : POSITIVE := 6 -- i.e., volume_values_per_step = 2**VOLUME_STEP_2 - ); - PORT ( - aclk : IN STD_LOGIC; - aresetn : IN STD_LOGIC; + GENERIC ( + TDATA_WIDTH : POSITIVE := 24; -- Width of input audio data (24-bit audio samples) + VOLUME_WIDTH : POSITIVE := 10; -- Width of volume control input (10-bit = 0-1023 range) + VOLUME_STEP_2 : POSITIVE := 6 -- Log2 of volume values per step (2^6 = 64 values per step) + ); + PORT ( + -- Clock and reset signals + aclk : IN STD_LOGIC; -- Main clock input + aresetn : IN STD_LOGIC; -- Active-low asynchronous reset - s_axis_tvalid : IN STD_LOGIC; - s_axis_tdata : IN STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); - s_axis_tlast : IN STD_LOGIC; - s_axis_tready : OUT STD_LOGIC; + -- AXI4-Stream Slave Interface (Audio Input) + s_axis_tvalid : IN STD_LOGIC; -- Input data valid signal + s_axis_tdata : IN STD_LOGIC_VECTOR(TDATA_WIDTH - 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 - m_axis_tvalid : OUT STD_LOGIC; - m_axis_tdata : OUT STD_LOGIC_VECTOR(TDATA_WIDTH - 1 + 2 ** (VOLUME_WIDTH - VOLUME_STEP_2 - 1) DOWNTO 0); - m_axis_tlast : OUT STD_LOGIC; - m_axis_tready : IN STD_LOGIC; + -- AXI4-Stream Master Interface (Audio Output with extended width) + m_axis_tvalid : OUT STD_LOGIC; -- Output data valid signal + m_axis_tdata : OUT STD_LOGIC_VECTOR(TDATA_WIDTH - 1 + 2 ** (VOLUME_WIDTH - VOLUME_STEP_2 - 1) DOWNTO 0); -- Extended width output data + m_axis_tlast : OUT STD_LOGIC; -- Channel indicator passthrough + m_axis_tready : IN STD_LOGIC; -- Downstream ready signal - volume : IN STD_LOGIC_VECTOR(VOLUME_WIDTH - 1 DOWNTO 0) - ); + -- Volume control input + volume : IN STD_LOGIC_VECTOR(VOLUME_WIDTH - 1 DOWNTO 0) -- Volume level (0=minimum, 1023=maximum) + ); END volume_multiplier; ARCHITECTURE Behavioral OF volume_multiplier IS - CONSTANT VOLUME_STEPS : INTEGER := (2 ** (VOLUME_WIDTH - 1)) / (2 ** VOLUME_STEP_2) + 1; - CONSTANT VOL_MID : INTEGER := 2 ** (VOLUME_WIDTH - 1); -- 512 for 10 bit - CONSTANT DEAD_ZONE : INTEGER := (2 ** VOLUME_STEP_2) / 2; + -- Calculate volume control parameters based on generics + CONSTANT VOLUME_STEPS : INTEGER := (2 ** (VOLUME_WIDTH - 1)) / (2 ** VOLUME_STEP_2) + 1; -- Number of volume steps (9 steps for 10-bit) + CONSTANT VOL_MID : INTEGER := 2 ** (VOLUME_WIDTH - 1); -- Center volume position (512 for 10-bit) + CONSTANT DEAD_ZONE : INTEGER := (2 ** VOLUME_STEP_2) / 2; -- Dead zone around center (32 for step size 64) - SIGNAL volume_exp_mult : INTEGER RANGE -VOLUME_STEPS TO VOLUME_STEPS := 0; + -- Volume scaling factor as exponential multiplier + -- Positive values = amplification (left shift), negative values = attenuation (right shift) + SIGNAL volume_exp_mult : INTEGER RANGE -VOLUME_STEPS TO VOLUME_STEPS := 0; - SIGNAL m_axis_tvalid_int : STD_LOGIC; + -- Internal AXI4-Stream signals + SIGNAL m_axis_tvalid_int : STD_LOGIC; -- Internal valid signal for output BEGIN - -- Assigning the output signals - m_axis_tvalid <= m_axis_tvalid_int; - s_axis_tready <= (m_axis_tready OR NOT m_axis_tvalid_int) AND aresetn; + -- Connect internal signals to output ports + m_axis_tvalid <= m_axis_tvalid_int; + + -- Input ready logic: Ready when downstream is ready OR no valid data pending, AND not in reset + -- This implements proper AXI4-Stream backpressure handling + s_axis_tready <= (m_axis_tready OR NOT m_axis_tvalid_int) AND aresetn; - -- Volume to exp process to avoid changing the volume value when multiplying it for the sample data - PROCESS (aclk) - BEGIN + -- Volume to exponent conversion process + -- Converts joystick position to bit-shift amount for exponential volume scaling + PROCESS (aclk) + BEGIN - IF rising_edge(aclk) THEN + IF rising_edge(aclk) THEN - IF aresetn = '0' THEN - volume_exp_mult <= 0; + IF aresetn = '0' THEN + -- Reset: Set to unity gain (no scaling) + volume_exp_mult <= 0; - ELSE - -- Volume to signed and centered and convert to power of 2 exponent - volume_exp_mult <= to_integer( - shift_right( - signed('0' & volume) - to_signed(VOL_MID - DEAD_ZONE, volume'length + 1), - VOLUME_STEP_2 - ) - ); + ELSE + -- Volume mapping and conversion to exponential scaling factor: + -- 1. Convert volume to signed value + -- 2. Center around middle position (VOL_MID) + -- 3. Apply dead zone offset for smooth center operation + -- 4. Divide by step size to get exponential scaling factor + -- + -- Volume Range Mapping: + -- volume = 0-479: Negative exponent (attenuation, right shift) + -- volume = 480-543: Zero exponent (unity gain, dead zone) + -- volume = 544-1023: Positive exponent (amplification, left shift) + volume_exp_mult <= to_integer( + shift_right( + signed('0' & volume) - to_signed(VOL_MID - DEAD_ZONE, volume'length + 1), + VOLUME_STEP_2 + ) + ); - END IF; + END IF; - END IF; + END IF; - END PROCESS; + END PROCESS; - -- Handle AXIS stream - PROCESS (aclk) - BEGIN + -- AXI4-Stream data processing + -- Applies exponential volume scaling using bit-shifting multiplication + PROCESS (aclk) + BEGIN - IF rising_edge(aclk) THEN + IF rising_edge(aclk) THEN - IF aresetn = '0' THEN - m_axis_tvalid_int <= '0'; - m_axis_tlast <= '0'; + IF aresetn = '0' THEN + -- Reset output interface + m_axis_tvalid_int <= '0'; -- No valid output data + m_axis_tlast <= '0'; -- Clear channel indicator - ELSE - -- Clear valid flag when master interface is ready - IF m_axis_tready = '1' THEN - m_axis_tvalid_int <= '0'; - END IF; + ELSE + -- Output handshake management: + -- Clear valid flag when downstream accepts data + IF m_axis_tready = '1' THEN + m_axis_tvalid_int <= '0'; + END IF; - -- Handle the data flow - IF s_axis_tvalid = '1' AND m_axis_tready = '1' THEN - -- Multiply the input data with the volume and assign to output - -- Joystick datasheet: (y-axis) a value of 0 when it is tilted all the way down - -- and a value of 1023 when it is tilted all the way up - IF volume_exp_mult >= 0 THEN - m_axis_tdata <= STD_LOGIC_VECTOR(shift_left(resize(signed(s_axis_tdata), m_axis_tdata'LENGTH), volume_exp_mult)); + -- Data processing: Apply volume scaling when both input and output are ready + IF s_axis_tvalid = '1' AND m_axis_tready = '1' THEN + -- Volume scaling using bit-shifting for exponential response: + -- + -- Joystick mapping (from datasheet): + -- Y-axis: 0 = tilted all the way down (minimum volume) + -- 1023 = tilted all the way up (maximum volume) + -- + -- Scaling method: + -- Positive exponent: Left shift = amplification (multiply by 2^n) + -- Negative exponent: Right shift = attenuation (divide by 2^n) + -- Zero exponent: No shift = unity gain (pass through) + + IF volume_exp_mult >= 0 THEN + -- Amplification: Left shift for volume boost + -- Resize to extended width first to prevent overflow + m_axis_tdata <= STD_LOGIC_VECTOR( + shift_left( + resize(signed(s_axis_tdata), m_axis_tdata'LENGTH), + volume_exp_mult + ) + ); - ELSE - m_axis_tdata <= STD_LOGIC_VECTOR(shift_right(resize(signed(s_axis_tdata), m_axis_tdata'LENGTH), - volume_exp_mult)); + ELSE + -- Attenuation: Right shift for volume reduction + -- Arithmetic right shift preserves sign for signed audio data + m_axis_tdata <= STD_LOGIC_VECTOR( + shift_right( + resize(signed(s_axis_tdata), m_axis_tdata'LENGTH), + - volume_exp_mult -- Convert negative to positive shift amount + ) + ); - END IF; + END IF; - m_axis_tvalid_int <= '1'; - m_axis_tlast <= s_axis_tlast; + -- Set output control signals + m_axis_tvalid_int <= '1'; -- Mark output data as valid + m_axis_tlast <= s_axis_tlast; -- Pass through channel indicator - END IF; + END IF; - END IF; + END IF; - END IF; + END IF; - END PROCESS; + END PROCESS; + + -- Example scaling factors (VOLUME_STEP_2 = 6, 64 values per step): + -- volume_exp_mult = -3: Divide by 8 (>>3) + -- volume_exp_mult = -2: Divide by 4 (>>2) + -- volume_exp_mult = -1: Divide by 2 (>>1) + -- volume_exp_mult = 0: No change (unity) + -- volume_exp_mult = +1: Multiply by 2 (<<1) + -- volume_exp_mult = +2: Multiply by 4 (<<2) + -- volume_exp_mult = +3: Multiply by 8 (<<3) END Behavioral; \ No newline at end of file diff --git a/LAB3/src/volume_saturator.vhd b/LAB3/src/volume_saturator.vhd index 10549d4..f1c063c 100644 --- a/LAB3/src/volume_saturator.vhd +++ b/LAB3/src/volume_saturator.vhd @@ -2,80 +2,123 @@ LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; USE IEEE.NUMERIC_STD.ALL; +-- Entity: volume_saturator +-- Purpose: Second stage of volume control pipeline - clips multiplication results to prevent overflow +-- Reduces bit width from extended multiplier output back to original audio format +-- Implements saturation (clipping) to prevent audio distortion from mathematical overflow ENTITY volume_saturator IS - GENERIC ( - TDATA_WIDTH : POSITIVE := 24; - VOLUME_WIDTH : POSITIVE := 10; - VOLUME_STEP_2 : POSITIVE := 6; -- i.e., number_of_steps = 2**(VOLUME_STEP_2) - HIGHER_BOUND : INTEGER := 2 ** 15 - 1; -- Inclusive - LOWER_BOUND : INTEGER := - 2 ** 15 -- Inclusive - ); - PORT ( - aclk : IN STD_LOGIC; - aresetn : IN STD_LOGIC; + GENERIC ( + TDATA_WIDTH : POSITIVE := 24; -- Width of final audio data output (24-bit audio samples) + VOLUME_WIDTH : POSITIVE := 10; -- Width of volume control input (10-bit = 0-1023 range) + VOLUME_STEP_2 : POSITIVE := 6; -- Log2 of volume values per step (2^6 = 64 values per step) + HIGHER_BOUND : INTEGER := 2 ** 15 - 1; -- Maximum positive value for saturation (inclusive) + LOWER_BOUND : INTEGER := - 2 ** 15 -- Maximum negative value for saturation (inclusive) + ); + PORT ( + -- Clock and reset signals + aclk : IN STD_LOGIC; -- Main clock input + aresetn : IN STD_LOGIC; -- Active-low asynchronous reset - s_axis_tvalid : IN STD_LOGIC; - s_axis_tdata : IN STD_LOGIC_VECTOR(TDATA_WIDTH - 1 + 2 ** (VOLUME_WIDTH - VOLUME_STEP_2 - 1) DOWNTO 0); - s_axis_tlast : IN STD_LOGIC; - s_axis_tready : OUT STD_LOGIC; + -- AXI4-Stream Slave Interface (Extended width input from multiplier) + s_axis_tvalid : IN STD_LOGIC; -- Input data valid signal + s_axis_tdata : IN STD_LOGIC_VECTOR(TDATA_WIDTH - 1 + 2 ** (VOLUME_WIDTH - VOLUME_STEP_2 - 1) DOWNTO 0); -- Wide input data from multiplier + s_axis_tlast : IN STD_LOGIC; -- Channel indicator (0=left, 1=right) + s_axis_tready : OUT STD_LOGIC; -- Ready to accept input data - m_axis_tvalid : OUT STD_LOGIC; - m_axis_tdata : OUT STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); - m_axis_tlast : OUT STD_LOGIC; - m_axis_tready : IN STD_LOGIC - ); + -- AXI4-Stream Master Interface (Original width output for audio) + m_axis_tvalid : OUT STD_LOGIC; -- Output data valid signal + m_axis_tdata : OUT STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0); -- Saturated audio sample output + m_axis_tlast : OUT STD_LOGIC; -- Channel indicator passthrough + m_axis_tready : IN STD_LOGIC -- Downstream ready signal + ); END volume_saturator; ARCHITECTURE Behavioral OF volume_saturator IS - CONSTANT HIGHER_BOUND_VEC : STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0) := STD_LOGIC_VECTOR(to_signed(HIGHER_BOUND, TDATA_WIDTH)); - CONSTANT LOWER_BOUND_VEC : STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0) := STD_LOGIC_VECTOR(to_signed(LOWER_BOUND, TDATA_WIDTH)); + -- Pre-calculated saturation bounds as STD_LOGIC_VECTORS for efficient comparison + -- These constants define the valid range for audio samples after volume processing + CONSTANT HIGHER_BOUND_VEC : STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0) := STD_LOGIC_VECTOR(to_signed(HIGHER_BOUND, TDATA_WIDTH)); + CONSTANT LOWER_BOUND_VEC : STD_LOGIC_VECTOR(TDATA_WIDTH - 1 DOWNTO 0) := STD_LOGIC_VECTOR(to_signed(LOWER_BOUND, TDATA_WIDTH)); - SIGNAL m_axis_tvalid_int : STD_LOGIC; + -- Internal AXI4-Stream control signal + SIGNAL m_axis_tvalid_int : STD_LOGIC; BEGIN - -- Output assignments - m_axis_tvalid <= m_axis_tvalid_int; - s_axis_tready <= (m_axis_tready OR NOT m_axis_tvalid_int) AND aresetn; - PROCESS (aclk) - BEGIN + -- Connect internal signals to output ports + m_axis_tvalid <= m_axis_tvalid_int; + + -- Input ready logic: Ready when downstream is ready OR no valid data pending, AND not in reset + -- This implements proper AXI4-Stream backpressure handling + s_axis_tready <= (m_axis_tready OR NOT m_axis_tvalid_int) AND aresetn; - IF rising_edge(aclk) THEN + -- Main saturation and data processing logic + PROCESS (aclk) + BEGIN - IF aresetn = '0' THEN - m_axis_tvalid_int <= '0'; - m_axis_tlast <= '0'; + IF rising_edge(aclk) THEN - ELSE - -- Clear valid flag when master interface is ready - IF m_axis_tready = '1' THEN - m_axis_tvalid_int <= '0'; - END IF; + IF aresetn = '0' THEN + -- Reset output interface + m_axis_tvalid_int <= '0'; -- No valid output data during reset + m_axis_tlast <= '0'; -- Clear channel indicator - -- Handle the data flow - IF s_axis_tvalid = '1' AND m_axis_tready = '1' THEN - -- Check if the input data is within the bounds else saturate - IF signed(s_axis_tdata) > signed(HIGHER_BOUND_VEC) THEN - m_axis_tdata <= HIGHER_BOUND_VEC; + ELSE + -- Normal operation - ELSIF signed(s_axis_tdata) < signed(LOWER_BOUND_VEC) THEN - m_axis_tdata <= LOWER_BOUND_VEC; + -- Output handshake management: + -- Clear valid flag when downstream accepts data + IF m_axis_tready = '1' THEN + m_axis_tvalid_int <= '0'; + END IF; - ELSE - m_axis_tdata <= STD_LOGIC_VECTOR(resize(signed(s_axis_tdata), TDATA_WIDTH)); - - END IF; + -- Data processing: Apply saturation when both input and output are ready + IF s_axis_tvalid = '1' AND m_axis_tready = '1' THEN + + -- Saturation Logic: + -- Check if the wide input data exceeds the valid audio range + -- If so, clip (saturate) to the nearest valid value + -- This prevents overflow distortion while preserving audio quality + + IF signed(s_axis_tdata) > signed(HIGHER_BOUND_VEC) THEN + -- Positive overflow: Clip to maximum positive value + -- This prevents "wraparound" distortion from mathematical overflow + m_axis_tdata <= HIGHER_BOUND_VEC; - m_axis_tvalid_int <= '1'; - m_axis_tlast <= s_axis_tlast; + ELSIF signed(s_axis_tdata) < signed(LOWER_BOUND_VEC) THEN + -- Negative overflow: Clip to maximum negative value + -- This prevents "wraparound" distortion from mathematical underflow + m_axis_tdata <= LOWER_BOUND_VEC; - END IF; + ELSE + -- Value within valid range: Resize to output width without clipping + -- This preserves the original audio quality when no overflow occurs + m_axis_tdata <= STD_LOGIC_VECTOR(resize(signed(s_axis_tdata), TDATA_WIDTH)); + + END IF; - END IF; + -- Set output control signals + m_axis_tvalid_int <= '1'; -- Mark output data as valid + m_axis_tlast <= s_axis_tlast; -- Pass through channel indicator - END IF; + END IF; - END PROCESS; + END IF; + + END IF; + + END PROCESS; + + -- Saturation Purpose and Benefits: + -- 1. Prevents audio distortion from mathematical overflow + -- 2. Maintains audio dynamic range within valid bit representation + -- 3. Reduces wide multiplication results back to standard audio format + -- 4. Provides "soft limiting" behavior for volume control + -- 5. Ensures compatibility with downstream audio processing blocks + -- + -- Without saturation, volume amplification could cause: + -- - Mathematical overflow leading to sign bit flips + -- - Severe audio distortion (crackling, popping sounds) + -- - Invalid audio sample values outside the representable range END Behavioral; \ No newline at end of file diff --git a/LAB3/vivado/LFO/LFO.xpr b/LAB3/vivado/LFO/LFO.xpr index 6467833..7206ae2 100644 --- a/LAB3/vivado/LFO/LFO.xpr +++ b/LAB3/vivado/LFO/LFO.xpr @@ -47,7 +47,7 @@