August Technical Puzzle

August Puzzle: The Mysterious Lock Loss

— SCENARIO: You’re debugging a Costas loop implementation that works

— perfectly in simulation but fails intermittently in hardware. 

— The loop locks quickly to F1 (carrier + 1kHz), but when the 

— input switches to F2 (carrier + 3kHz), it sometimes loses lock

— entirely instead of reacquiring.

— PUZZLE: What’s causing this intermittent lock loss? 

— HINT: Look carefully at the loop filter characteristics and gain scheduling.

library IEEE;

use IEEE.STD_LOGIC_1164.ALL;

use IEEE.NUMERIC_STD.ALL;

use IEEE.MATH_REAL.ALL;

entity costas_loop_puzzle is

    generic (

        DATA_WIDTH : integer := 16;

        PHASE_WIDTH : integer := 12;

        F1_OFFSET : integer := 1000;  — 1 kHz offset from carrier

        F2_OFFSET : integer := 3000   — 3 kHz offset from carrier

    );

    port (

        clk         : in  std_logic;

        reset       : in  std_logic;

        rf_input    : in  signed(DATA_WIDTH-1 downto 0);

        freq_select : in  std_logic; — ‘0’ for F1, ‘1’ for F2

        — Outputs for debugging

        i_data      : out signed(DATA_WIDTH-1 downto 0);

        q_data      : out signed(DATA_WIDTH-1 downto 0);

        phase_error : out signed(DATA_WIDTH-1 downto 0);

        vco_freq    : out signed(PHASE_WIDTH-1 downto 0);

        lock_detect : out std_logic

    );

end entity;

architecture behavioral of costas_loop_puzzle is

    — VCO signals

    signal vco_phase : signed(PHASE_WIDTH-1 downto 0) := (others => ‘0’);

    signal vco_i, vco_q : signed(DATA_WIDTH-1 downto 0);

    signal vco_control : signed(DATA_WIDTH-1 downto 0) := (others => ‘0’);

    — Mixer outputs

    signal mixer_i, mixer_q : signed(DATA_WIDTH-1 downto 0);

    — Loop filter components

    signal integrator : signed(DATA_WIDTH+4-1 downto 0) := (others => ‘0’);

    signal proportional : signed(DATA_WIDTH-1 downto 0);

    signal error_signal : signed(DATA_WIDTH-1 downto 0);

    — Lock detection

    signal error_magnitude : unsigned(DATA_WIDTH-1 downto 0);

    signal lock_counter : unsigned(15 downto 0) := (others => ‘0’);

    — Critical parameters (this is where the puzzle lies!)

    constant KP : signed(7 downto 0) := to_signed(32, 8);   — Proportional gain

    constant KI : signed(7 downto 0) := to_signed(2, 8);    — Integral gain

    — Gain scheduling based on frequency (THE TRAP!)

    signal adaptive_kp : signed(7 downto 0);

    signal adaptive_ki : signed(7 downto 0);

begin

    — Gain scheduling logic – reduces gains at higher frequencies

    — This looks reasonable but creates the error starvation!

    process(freq_select)

    begin

        if freq_select = ‘0’ then  — F1 mode

            adaptive_kp <= KP;

            adaptive_ki <= KI;

        else  — F2 mode – “optimize” for stability at higher frequency

            adaptive_kp <= shift_right(KP, 2);  — KP/4

            adaptive_ki <= shift_right(KI, 3);  — KI/8

        end if;

    end process;

    — VCO phase accumulator

    process(clk, reset)

    begin

        if reset = ‘1’ then

            vco_phase <= (others => ‘0’);

        elsif rising_edge(clk) then

            vco_phase <= vco_phase + vco_control;

        end if;

    end process;

    — VCO sine/cosine generation (simplified)

    — In real implementation, this would be a lookup table

    vco_i <= to_signed(integer(32767.0 * cos(real(to_integer(vco_phase)) * MATH_PI / 2048.0)), DATA_WIDTH);

    vco_q <= to_signed(integer(32767.0 * sin(real(to_integer(vco_phase)) * MATH_PI / 2048.0)), DATA_WIDTH);

    — Quadrature mixers

    process(clk)

    begin

        if rising_edge(clk) then

            — Multiply and low-pass filter (simplified)

            mixer_i <= shift_right(rf_input * vco_i, 15);

            mixer_q <= shift_right(rf_input * vco_q, 15);

        end if;

    end process;

    — Costas loop error detector (classic I*sign(Q) approach)

    process(clk)

        variable q_sign : signed(DATA_WIDTH-1 downto 0);

    begin

        if rising_edge(clk) then

            if mixer_q >= 0 then

                q_sign := to_signed(1, DATA_WIDTH);

            else

                q_sign := to_signed(-1, DATA_WIDTH);

            end if;

            error_signal <= shift_right(mixer_i * q_sign, 8);

        end if;

    end process;

    — Loop filter with adaptive gains

    process(clk, reset)

        variable scaled_error : signed(DATA_WIDTH+4-1 downto 0);

        variable prop_term : signed(DATA_WIDTH+4-1 downto 0);

    begin

        if reset = ‘1’ then

            integrator <= (others => ‘0’);

            vco_control <= (others => ‘0’);

        elsif rising_edge(clk) then

            — Scale error by adaptive gains

            scaled_error := resize(error_signal * adaptive_ki, DATA_WIDTH+4);

            prop_term := resize(error_signal * adaptive_kp, DATA_WIDTH+4);

            — Integrate with adaptive gain

            integrator <= integrator + scaled_error;

            — PI controller output

            vco_control <= resize(shift_right(integrator + prop_term, 4), DATA_WIDTH);

        end if;

    end process;

    — Lock detector – measures error magnitude

    process(clk, reset)

    begin

        if reset = ‘1’ then

            lock_counter <= (others => ‘0’);

            lock_detect <= ‘0’;

        elsif rising_edge(clk) then

            error_magnitude <= unsigned(abs(error_signal));

            if error_magnitude < 100 then  — Low error threshold

                if lock_counter < 65535 then

                    lock_counter <= lock_counter + 1;

                end if;

            else

                lock_counter <= (others => ‘0’);

            end if;

            — Declare lock after 1000 consecutive low-error samples

            if lock_counter > 1000 then

                lock_detect <= ‘1’;

            else

                lock_detect <= ‘0’;

            end if;

        end if;

    end process;

    — Output assignments

    i_data <= mixer_i;

    q_data <= mixer_q;

    phase_error <= error_signal;

    vco_freq <= resize(vco_control, PHASE_WIDTH);

end behavioral;

Leave a Reply

Your email address will not be published. Required fields are marked *