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)
begin
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
begin
if rising_edge(clk_t) then
if bit_cnt < 200 then
bit_cnt <= bit_cnt + 1;
else
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:
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.
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:
I think something about my approach might be wrong, so any advice would be appreciated.