Why does the constellation rotate when using OFDM Symbol Acquisition block in this flow graph below ?

The only explanation I could think of would be carrier frequency offset but in this flowgraph there is no carrier offset, so how can the constellation rotate if there is no carrier offset ?

I think the OFDM Symbol Acquisition block only finds where the OFDM symbol is (by finding the cyclic prefix using the autocorrelation function - I believe) and does not do any further magic.

I am asking this because I am trying to demodulate a recorded OFDM signal for which I do not know the Sync Word but I can do carrier synchronization "by hand", so the OFDM Symbol Acquisition block would be useful in this case.

Here is the GRC file : http://pastebin.com/2WvRG2Ly

enter image description here

enter image description here

EDIT: Inspired by Marcus' answer I made the following simpler flow graph: enter image description here Which gives this result: enter image description here

This is not spinning anymore :)

Now I wonder why is the constellation so noisy if there is no noisy channel?

It should not be noisy right ?


enter image description here

enter image description here

It seems to be noisy because the source is noisy, strange. Why is that ?

EDIT3: The source noisiness comes from the RRC filter.


2 Answers 2


Got me at that one! The "OFDM symbol acquisition" block is in fact not from gr-digital (where your other OFDM blocks come frome), but from gr-dtv, where it is used to capture DVB-T signals, if I remember correctly. It might be very DVB-specific! Let us have a look at the dvbt_rx_8k.grc example from gr-dtv (or, at least, the top half):


So your understanding is absolutely right, OFDM symbol acquisition just does some syncing.

This time, I'll leave a bit more up to your own research (as finishing this up as a nice answer will probably take quite some time), and will concentrate on the steps I took to figure out how the block works. I hope this is most helpful to you, and of course helpful for future answer-seekers trying get the general grasp on how to find out how GNU Radio blocks "tick". Looking at your flow graph, I'm pretty sure you've done most of this already, but as I mentioned, I'd like to illustrate the "investigative principle" for others.

  • I figured out how the underlying block is called. Typically, the default name of a new block of that type gives that away, or the documentation type. So dtv_dvbt_ofdm_sym_acquisition was what I found.
  • Then I went to the GNU Radio source repo clone and used git to figure out where to look (I'll annotate the result with a bit of hints):
$ git ls-files|grep ofdm_sym_acquisition
gr-dtv/grc/dtv_dvbt_ofdm_sym_acquisition.xml ##.xml: the file describing how this block looks in GRC
gr-dtv/include/gnuradio/dtv/dvbt_ofdm_sym_acquisition.h ##gr-something/include/: public interface, the one accessible and documented in Doxygen
gr-dtv/lib/dvbt/dvbt_ofdm_sym_acquisition_impl.cc ##_impl.cc,h: the actual implementation, which has all the "ugly" details, private variables and stuff
  • For us, the _impl is of most relevance. So I opened it and looked for a work or general_work method right away (for difference between these, refer to the Guided Tutorials part 4)
  • The (general_)work method is the central workhorse of a GNU Radio block: GNU Radio's scheduler will call it whenever there are input items to process (given that there's enough output buffer space). So I skimmed it. Pasting it here, trying to delete all lines that I didn't care about at first. Find my "mental" annotations in between.
    dvbt_ofdm_sym_acquisition_impl::general_work (int noutput_items,

The usual stuff: input items are in in, output goes to out:

        const gr_complex *in = (const gr_complex *) input_items[0];
        gr_complex *out = (gr_complex *) output_items[0];

Ah-Ha! We loop over something, let's keep an eye out for the i.

        for (int i = 0; i < noutput_items; i++) {

ok, judging by the variables name this is the case of us not having "locked" on to sync sometime. Let's concentrate on this and ignore the other case for now.

          if (!d_initial_acquisition) {

This looks a lot as if the interesting work is done in ml_sync. d_cp_length definitely points to us having to deal with a system that uses cyclic prefixes.

            d_initial_acquisition = ml_sync(&in[d_consumed], 2 * d_fft_length + d_cp_length - 1, d_fft_length + d_cp_length - 1, \
                &d_cp_start, &d_derot[0], &d_to_consume, &d_to_out);
            d_cp_found = d_initial_acquisition;
          else {

I decide to break here and take a look at ml_sync.

The name is pretty revealing already; it's probably going to be a Maximum Likelihood Synchronizer.

So, going up in the source, we find ml_sync. It's pretty well-commented and uses (VOLK = Vector optimized library of kernels) functions that have names like magnitude_squared; I think I'll leave the lecture up to you and give you my quick understanding of it:

  • volk_32fc_magnitude_squared_32f(&d_norm[low], &in[low], size); calculates the mag² == norm of the input, starting at the low position, saves it in d_norm
  • The "moving sum" loop calculates the "amount" of correlation and the signal energy in windows of size d_fft_length; that makes sense. A high power signal can have a very high correlation coefficient with something that is not really "similar", so you need to "normalize" later on. Results are stored in d_gamma and d_phi, respectively.
  • The magnitudes of the windowed-correlation vector elements in d_gamma are stored in d_lambda, and something linearly dependend of d_phi is subtracted from those.
  • Peak search in d_lambda.
  • From the peak position, a frequency correction is calculated. From the correlation coefficients, phase-derotation is done.

So what we have here is an autocorrelation-based synchronizer that locks onto the cyclic prefix. Nice!

Comparing this with Schmidl&Cox that was used in ofdm_rx.grc, calculating the autocorrelation is of course pretty CPU-intense, and we need as much Cyclic Prefix as we can get. However, this might be (is) a suitable synchronizer for the typical broadcast channel, where FFTs are long, and so are prefixes, and the trade-off between being fast at decoding packets and not missing a single packets is biased in favor of a RX-maximizing receiver operation characteristic.

To the question why this doesn't work in your setup, I must admit that it's a bit over my head. My intuition tells me that there might be ambiguities due to your 1/4 payload/prefix ratio, and you consistently lock onto the middle of two consecutive frames or so.

The point is: try!

  • 1
    $\begingroup$ Many thanks for the detailed explanation Marcus, this should point me to the right direction. I need to dig a little bit deeper. Perhaps I write my own OFDM symbol acquisition block in numpy and see what that gives. $\endgroup$
    – jhegedus
    Commented May 13, 2016 at 10:56

While I did not implement your blocks excatly to closely look at what's happening, I think the reason is the following.

First, the noisy samples shouldn't be noisy because OFDM works with raw symbols; in your second flowgraph, you are using pulse shaped symbol stream which is bound to generate such clouds around the original values.

Second, the SNR difference between your first and second flowgraph is huge, being 1 and 30 dB, respectively. With low SNR, you might be locking on to a sample to the left or right of the original timing point. Going in the frequency domain next, a wrong reference point (shift in time) invokes a complex exponential in frequency whose period depends on the timing offset. And that is why you are seeing the rotation.

Increase the SNR sufficiently high in your first flowgraph and see if it is still spinning.


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.