I am currently trying to implement a Costas Loop in order to demodulate a BPSK signal. Here is the reference block diagram I have been using to design the loop:Costas Loop Block Diagram

I originally tried to make this all in VHDL code, but because I ended up using almost entirely IP cores, I decided to make it in Vivado's IP integrator:Vivado IP Integrator Block Diagram

From my research online, I found the low pass filter for the I and Q arm can be a simple SRRC filter using a FIR design. The cutoff frequency just needs to be small enough to eliminate the summation of the NCO frequency and the BPSK frequency, while not eliminating the difference between these frequencies. I used python to come up with good filter taps. Here is a screenshot of the IP core settings I was using as well as the frequency response:Low Pass Filter(LPF) Settings

For the NCO, I used Xilinx's DDS core to act as a phase accumulator and SIN/COS LUT. The DDS core will generate two tones that are 90 degrees out of phase which I use for the I/Q arms. Here are the settings I used for the core:DDS Settings; I don't have enough reputation on stack overflow for 9 screenshots so I will just say that the core is in phase increment streaming mode and phase offset is turned off.

Lastly, comes the loop filter, which I believe is the heart of the issue, but I will get into that in a bit. I have read several papers on what the appropriate design for this filter is; some say it has to be a FIR LPF, some say it has to be an IIR LPF, some say it should be PID contol filter, but most say it has to be a proportional + integral(PI) filter. Going off of that here is the reference design I using:PI Loop Filter Block Diagram. Regarding the integrator path, I handled the delay by making that adder synchronous (added a clock signal to it). I should also note that all the Adder/Subtracter blocks in Vivado are set to the Adding mode, not the subtracting mode.

That is pretty much the whole design, now I will finish up with simulation set up and results. Here is my test bench for generating a BPSK signal and providing gain values for the PI filter: `

LO_inst : dds_compiler_1
    PORT MAP (
        aclk => clk_t,
        s_axis_phase_tvalid => '1',
        s_axis_phase_tdata => x"0000" & x"1999", --zero phase offset, set BPSK frequency to fs/5
        m_axis_data_tvalid => VCO_valid,
        m_axis_data_tdata => VCO_OUT

Demodulator_inst : Demod_wrapper
  port map (
    Data_IN => Data_IN_t(15 downto 8), --uses SIN half of signal
    alpha => "0010" & "0000" & "0000" & "0000", --divide by 2^16 to get gain value
    beta => "0010" & "0000" & "0000" & "0000", --divide by 2^16 to get gain value
    clk => clk_t

BPSK_mod : process (bit_stream, VCO_OUT)
    case (bit_stream) is
        when '0' =>
            Data_IN_t <= VCO_OUT;
        when '1' =>
            Data_IN_t <= not VCO_OUT;
        when others =>
            Data_IN_t <= VCO_OUT;
    end case;
end process BPSK_mod;

toggle_bit : process (clk_t) is
    if rising_edge(clk_t) then
        if bit_cnt < 200 then
            bit_cnt <= bit_cnt + 1;
            bit_cnt <= 0;
            bit_stream <= not bit_stream;
        end if;
    end if;
end process;

` The DDS module in the test bench is set up the exact same way as the IP core except I added the ability to do a phase offset to show phase locking. Every 200 clock cycles (clock = 250MHz by the way) the signal switches 180 degrees in phase. Here are the simulation waveforms:Sim screenshot 1 Sim screenshot 2 Sim screenshot 3

From these results, I can see the SRRC filters are doing their job by eliminating those faster oscillation present on the arms and I know the DDS module is acting the way it should as well. This leads me to think this is a loop filter issue. In the papers I've read the output of the loop filter seem to increase until it got to a nice steady state value where it was frequency and phase locked.

Any insight as to why this is happening would be greatly appreciated. I hope I articulated the problem well, but if I didn't please let me know what wasn't clear.

Update1: Per Dan's helpful instructions I tried a few things. I started of by setting the integrator and proportional gain to zero, I then increased the proportional gain, while leaving I = 0 (in my code I call it beta), until I was able to see the system phase lock match the signal and the NCO. I was able to get it to lock with the following gain values: I = 0 and P = -2^-4

Demodulator_inst : Demod_wrapper
  port map (
    Data_IN => Data_IN_t(15 downto 8),
    alpha => "1111" & "0000" & "0000" & "0000", --divide by 2^16 to get gain value
    beta => "0000" & "0000" & "0000" & "0000", --divide by 2^16 to get gain value
    clk => clk_t

I also moved some of the waveforms around and updated the block diagram I was using so it would be easier to follow. Here is the updated block diagram and the waveforms showing it locks:enter image description hereenter image description hereenter image description here

You might notice it takes a while to lock, but when I increase the P gain it locks faster. I wasn't sure if I was was supposed to find the minimum absolute value of P before it locks. I also didn't notice anything that looked like gain dependent error so that might be an indication I did something wrong.

Now comes the part where I am still kind of stuck. The next step I did was increase the frequency of the input signal until I see the loop filter output start to oscillate. Originally the input frequency was 25MHz. It began to oscillate when the input frequency was ±84kHz which seems pretty small to me.enter image description here

I pulled back the offset frequency by half and started to increment my integrator gain. Although even at the smallest value I = 2^-16 the output of the loop filter diverges:enter image description here

I think something about my approach might be wrong, so any advice would be appreciated.

  • $\begingroup$ a PI compensator is an IIR filter, while the converse is not true. That being said, using an FIR as a compensator is something new for me. Perhaps, an FIR with a high DC gain is almost the same as a P controller. $\endgroup$
    – Ben
    Feb 25, 2020 at 3:11
  • $\begingroup$ Oh, I see. Thanks for letting me know. I believe the integrator is needed for frequency matching, so I don't think a P controller/FIR filter would work. But I could be wrong. $\endgroup$
    – Jake
    Feb 25, 2020 at 3:29
  • 1
    $\begingroup$ It would be helpful if you could label your waveforms with the same node names as in your Costas Loop block diagram--- it's not easy to follow what is what. $\endgroup$ Feb 25, 2020 at 3:45
  • 1
    $\begingroup$ ok I followed the plots and they seem to make sense-- one thing you can try is to manually tune your filter by starting with I = 0 (turn of the integrator path basically) and get it to lock with P alone (it will have a gain dependent error, not the way you want to run finally but just to see if it will converge- be careful that you don't have a wrong sign on your feedback; easiest is to invert it if it doesn't correctly lock. Once that works increase to threshold of oscillation and then back it off to half value, then slowly bring up I while observing your settling time /overshoot etc $\endgroup$ Feb 25, 2020 at 3:59
  • 1
    $\begingroup$ Also consider plotting the NCO output right alongside the input signal or even superimposed on the same plot- it will be easier to see if it is working / converging properly $\endgroup$ Feb 25, 2020 at 5:01

1 Answer 1


The low pass filters used must have cutoff frequencies at least 10x higher than the PLL loop bandwidth and of minimum delay so as to not affect loop stability. I recommend using minimum phase filters and NOT linear phase filters for this purpose. The loop itself acts as a low pass filter so the filters after the multipliers need not be of high order. Minimizing delay here is critical.

To “tune” the PI filter to it’s maximum bandwidth with regards to stability, set both P and I to zero and then slowly increase P up to the threshold of instability, then divide this by two to use as the initial value for P. With I zero this will lock but will have a static offset error. To eliminate the offset error slowly increase I with P set to the value we just determined (as half the value at instability). Increase I up to the threshold of instability and then divide this by two to use as the initial value for I. This should tune the loop to maximum bandwidth close to the ideal damping factor of 0.7 and represents the fastest tracking that can be achieved (as limited by loop delay and component gains). From this point I and P can be adjusted further in small increments to optimize frequency and time responses.

Note importantly for a carrier recovery loop we may NOT want the fastest tracking achievable: thr Costas Loop is often integrated together with the waveform demodulation and if we track too fast then the loop will track out the phase modulation in the signal. A good rule of thumb is to set the tracking loop bandwidth to be 1/20 to 1/50 of the symbol rate for the modulation used. This is best determined from an actual loop model characterizing the implementation but the loop bandwidth can also be experimentally measured easily by adding a sinusoidal phase modulation of constant peak to peak magnitude to the carrier and observing the error signal at the input to the loop filter (the -3 dB BW is when the tracking error peak to peak amplitude drops 3 dB). I’ve also done quick step response tests (step the carrier by a small step, big enough to observe the response) and knowing that the BW is related to the 10% to 90% response time as $BW = t_r/35$ with $BW$ as the loop bandwidth in Hz and $t_r$ in seconds.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.