diff --git a/docs/source/conf.py b/docs/source/conf.py index 7aff862..02eecc3 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -158,9 +158,20 @@ texinfo_documents = [ ] - - # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'https://docs.python.org/': None} +# ============================================================= +# Custom configurations +# ============================================================= +# +# Enable figure numbering numfig = True + +# global macros +rst_prolog = """ +.. |project| replace:: OpenOFDM +.. |us| replace:: :math:`\mu s` +""" + +math_number_all = True diff --git a/docs/source/detection.rst b/docs/source/detection.rst index 5078a10..3b01d82 100644 --- a/docs/source/detection.rst +++ b/docs/source/detection.rst @@ -12,7 +12,7 @@ for coarse frequency offset correction , which will be discussed separately in Power Trigger ------------- -- **Module**: ``power_trigger.v`` +- **Module**: :file:`power_trigger.v` - **Input**: ``sample_in`` (16B I + 16B Q), ``sample_in_strobe`` (1B) - **Output**: ``trigger`` (1B) - **Setting Registers**: ``SR_POWER_THRES``, ``SR_POWER_WINDOW``, @@ -24,7 +24,7 @@ we are trying to detect short preamble from "meaningful" signals. One example of "un-meaningful" signal is constant power levels, whose auto correlation metric is also very high (nearly 1) but obviously does not represent packet beginning. -The first module in the pipeline is the ``power_trigger.v``. It takes the I/Q +The first module in the pipeline is the :file:`power_trigger.v`. It takes the I/Q samples as input and asserts the ``trigger`` signal during a potential packet activity. Optionally, it can be configured to skip the first certain number of samples before detecting a power trigger. This is useful to skip the spurious @@ -39,7 +39,7 @@ continuous samples. Short Preamble Detection ------------------------ -- **Module**: ``sync_short.v`` +- **Module**: :file:`sync_short.v` - **Input**: ``sample_in`` (16B I + 16B Q), ``sample_in_strobe`` (1B) - **Output**: ``short_preamble_detected`` (1B) - **Setting Registers**: ``SR_MIN_PLATEAU`` @@ -74,6 +74,21 @@ preamble can be declared. Auto Correlation of the Short Preamble samples (N=48). +To plot :numref:`fig_corr`, load the samples (see :ref:`sec_sample`), then: + +.. code-block:: python + + from matplotlib import pyplot as plt + + fig, ax = plt.subplots(nrows=2, ncols=1, sharex=True) + ax[0].plot([s.real for s in samples[:500]], '-bo') + ax[1].plot([abs(sum([samples[i+j]*samples[i+j+16].conjugate() + for j in range(0, 48)]))/ + sum([abs(samples[i+j])**2 for j in range(0, 48)]) + for i in range(0, 500)], '-ro') + plt.show() + + :numref:`fig_corr` shows the auto correlation value of the samples in :numref:`fig_short_preamble`. We can see that the correlation value is almost 1 during the short preamble period, but drops quickly after that. We can also see diff --git a/docs/source/files/802.11-2012.pdf b/docs/source/files/802.11-2012.pdf new file mode 100644 index 0000000..666b97b Binary files /dev/null and b/docs/source/files/802.11-2012.pdf differ diff --git a/docs/source/files/lts.txt b/docs/source/files/lts.txt new file mode 100644 index 0000000..d2441b8 --- /dev/null +++ b/docs/source/files/lts.txt @@ -0,0 +1,128 @@ +-1.559999999999999998e-01 +0.000000000000000000e+00 +1.200000000000000025e-02 +-9.800000000000000377e-02 +9.199999999999999845e-02 +-1.059999999999999970e-01 +-9.199999999999999845e-02 +-1.150000000000000050e-01 +-3.000000000000000062e-03 +-5.399999999999999939e-02 +7.499999999999999722e-02 +7.399999999999999634e-02 +-1.270000000000000018e-01 +2.100000000000000130e-02 +-1.219999999999999973e-01 +1.700000000000000122e-02 +-3.500000000000000333e-02 +1.509999999999999953e-01 +-5.600000000000000117e-02 +2.199999999999999872e-02 +-5.999999999999999778e-02 +-8.100000000000000255e-02 +7.000000000000000666e-02 +-1.400000000000000029e-02 +8.200000000000000344e-02 +-9.199999999999999845e-02 +-1.310000000000000053e-01 +-6.500000000000000222e-02 +-5.700000000000000205e-02 +-3.899999999999999994e-02 +3.699999999999999817e-02 +-9.800000000000000377e-02 +6.199999999999999956e-02 +6.199999999999999956e-02 +1.189999999999999947e-01 +4.000000000000000083e-03 +-2.199999999999999872e-02 +-1.610000000000000042e-01 +5.899999999999999689e-02 +1.499999999999999944e-02 +2.400000000000000050e-02 +5.899999999999999689e-02 +-1.370000000000000107e-01 +4.700000000000000011e-02 +1.000000000000000021e-03 +1.150000000000000050e-01 +5.299999999999999850e-02 +-4.000000000000000083e-03 +9.800000000000000377e-02 +2.599999999999999881e-02 +-3.799999999999999906e-02 +1.059999999999999970e-01 +-1.150000000000000050e-01 +5.500000000000000028e-02 +5.999999999999999778e-02 +8.799999999999999489e-02 +2.100000000000000130e-02 +-2.800000000000000058e-02 +9.700000000000000289e-02 +-8.300000000000000433e-02 +4.000000000000000083e-02 +1.110000000000000014e-01 +-5.000000000000000104e-03 +1.199999999999999956e-01 +1.559999999999999998e-01 +0.000000000000000000e+00 +-5.000000000000000104e-03 +-1.199999999999999956e-01 +4.000000000000000083e-02 +-1.110000000000000014e-01 +9.700000000000000289e-02 +8.300000000000000433e-02 +2.100000000000000130e-02 +2.800000000000000058e-02 +5.999999999999999778e-02 +-8.799999999999999489e-02 +-1.150000000000000050e-01 +-5.500000000000000028e-02 +-3.799999999999999906e-02 +-1.059999999999999970e-01 +9.800000000000000377e-02 +-2.599999999999999881e-02 +5.299999999999999850e-02 +4.000000000000000083e-03 +1.000000000000000021e-03 +-1.150000000000000050e-01 +-1.370000000000000107e-01 +-4.700000000000000011e-02 +2.400000000000000050e-02 +-5.899999999999999689e-02 +5.899999999999999689e-02 +-1.499999999999999944e-02 +-2.199999999999999872e-02 +1.610000000000000042e-01 +1.189999999999999947e-01 +-4.000000000000000083e-03 +6.199999999999999956e-02 +-6.199999999999999956e-02 +3.699999999999999817e-02 +9.800000000000000377e-02 +-5.700000000000000205e-02 +3.899999999999999994e-02 +-1.310000000000000053e-01 +6.500000000000000222e-02 +8.200000000000000344e-02 +9.199999999999999845e-02 +7.000000000000000666e-02 +1.400000000000000029e-02 +-5.999999999999999778e-02 +8.100000000000000255e-02 +-5.600000000000000117e-02 +-2.199999999999999872e-02 +-3.500000000000000333e-02 +-1.509999999999999953e-01 +-1.219999999999999973e-01 +-1.700000000000000122e-02 +-1.270000000000000018e-01 +-2.100000000000000130e-02 +7.499999999999999722e-02 +-7.399999999999999634e-02 +-3.000000000000000062e-03 +5.399999999999999939e-02 +-9.199999999999999845e-02 +1.150000000000000050e-01 +9.199999999999999845e-02 +1.059999999999999970e-01 +1.200000000000000025e-02 +9.800000000000000377e-02 diff --git a/docs/source/files/samples.dat b/docs/source/files/samples.dat new file mode 100644 index 0000000..a0177fd Binary files /dev/null and b/docs/source/files/samples.dat differ diff --git a/docs/source/freq_offset.rst b/docs/source/freq_offset.rst index 14b1df5..e17b48d 100644 --- a/docs/source/freq_offset.rst +++ b/docs/source/freq_offset.rst @@ -3,14 +3,14 @@ Frequency Offset Correction =========================== -This paper [1]_ explains why frequency offset occurs and how to correct it. In a -nutshell, there are two types of frequency offsets. The first is called -**Carrier Frequency Offset (CFO)** and is caused by the difference between the -transmitter and receiver's Local Oscillator (LO). This symptom of this offset is -a phase rotation of incoming I/Q samples (time domain). The second is **Sampling -Frequency Offset (SFO)** and is caused by the sampling effect. The symptom of -this offset is a phase rotation of constellation points after FFT (frequency -domain). +:download:`This paper ` [1]_ explains why +frequency offset occurs and how to correct it. In a nutshell, there are two +types of frequency offsets. The first is called **Carrier Frequency Offset +(CFO)** and is caused by the difference between the transmitter and receiver's +Local Oscillator (LO). This symptom of this offset is a phase rotation of +incoming I/Q samples (time domain). The second is **Sampling Frequency Offset +(SFO)** and is caused by the sampling effect. The symptom of this offset is a +phase rotation of constellation points after FFT (frequency domain). The CFO can be corrected with the help of short preamble (Coarse) long preamble (Fine). And the SFO can be corrected using the pilot sub-carriers in each OFDM @@ -64,10 +64,10 @@ long preamble) are corrected as: S'[m] = S[m]e^{-jm\alpha_{ST}}, m = 0, 1, 2, \ldots -In OpenOFDM, the coarse CFO is calculated in the ``sync_short`` module, and we +In |project|, the coarse CFO is calculated in the ``sync_short`` module, and we set :math:`N=64`. The ``prod_avg`` in :numref:`fig_sync_short` is fed into a ``moving_avg`` module with window size set to 64. -.. [1] Sourour, Essam, Hussein El-Ghoroury, and Dale McNeill. "Frequency Offset Estimation and Correction in the IEEE 802.11 a WLAN." Vehicular Technology Conference, 2004. VTC2004-Fall. 2004 IEEE 60th. Vol. 7. IEEE, 2004. +.. [1] Sourour, Essam, Hussein El-Ghoroury, and Dale McNeill. "Frequency Offset Estimation and Correction in the IEEE 802.11 a WLAN." Vehicular Technology Conference, 2004. VTC2004-Fall. 2004 IEEE 60th. Vol. 7. IEEE, 2004. diff --git a/docs/source/images/corr.png b/docs/source/images/corr.png index 79fd17d..e30e405 100644 Binary files a/docs/source/images/corr.png and b/docs/source/images/corr.png differ diff --git a/docs/source/images/lts.png b/docs/source/images/lts.png new file mode 100644 index 0000000..ec200e4 Binary files /dev/null and b/docs/source/images/lts.png differ diff --git a/docs/source/images/match_size.png b/docs/source/images/match_size.png new file mode 100644 index 0000000..06f844d Binary files /dev/null and b/docs/source/images/match_size.png differ diff --git a/docs/source/images/short_preamble.png b/docs/source/images/short_preamble.png index 824764e..7777c83 100644 Binary files a/docs/source/images/short_preamble.png and b/docs/source/images/short_preamble.png differ diff --git a/docs/source/images/training.png b/docs/source/images/training.png new file mode 100644 index 0000000..ec62306 Binary files /dev/null and b/docs/source/images/training.png differ diff --git a/docs/source/index.rst b/docs/source/index.rst index ddb1a52..8a0c7a7 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -3,15 +3,17 @@ You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -Welcome to OpenOFDM's documentation! -==================================== +Welcome to |project|'s documentation! +===================================== .. toctree:: :maxdepth: 2 :caption: Contents: + sample detection freq_offset + sync_long setting verilog diff --git a/docs/source/sample.rst b/docs/source/sample.rst new file mode 100644 index 0000000..e45ddbf --- /dev/null +++ b/docs/source/sample.rst @@ -0,0 +1,19 @@ +.. _sec_sample: + +Sample File +=========== + +Throughout this documentation we will be using a sample file that contains the +I/Q samples of a 802.11a packet at 24 Mbps (16-QAM). It'll be helpful to use a +interactive iPython session and exercise various steps discussed in the +document. + +Download the sample file from :download:`here `, the data +can be loaded as follows: + +.. code-block:: python + + import scipy + + wave = scipy.fromfile('samples.dat', dtype=scipy.int16) + samples = [complex(i, q) for i, q in zip(wave[::2], wave[1::2])] diff --git a/docs/source/setting.rst b/docs/source/setting.rst index 4958dba..08e98a1 100644 --- a/docs/source/setting.rst +++ b/docs/source/setting.rst @@ -6,7 +6,7 @@ Setting Registers - **Output**: ``out``, ``changed`` To enable dynamic configuration of decoding parameters at runtime, the USRP N210 -provides the setting register mechanism. Most modules in OpenOFDM have three +provides the setting register mechanism. Most modules in |project| have three common inputs for such purpose: - ``set_stb (1)``: asserts high when the setting data is valid @@ -14,9 +14,9 @@ common inputs for such purpose: - ``set_data (32)``: the register value -Here is a list of setting registers in OpenOFDM. +Here is a list of setting registers in |project|. -.. table:: List of Setting Registers in OpenOFDM. +.. table:: List of Setting Registers in |project|. :align: center +-----------------+------+-----------------+-----------+---------------+---------------------------------------------------------------+ diff --git a/docs/source/sync_long.rst b/docs/source/sync_long.rst new file mode 100644 index 0000000..dd08526 --- /dev/null +++ b/docs/source/sync_long.rst @@ -0,0 +1,101 @@ +Symbol Alignment +================ + +After detecting the packet, the next step is to determine precisely where each +OFDM symbol starts. In 802.11, each OFDM symbol is 4 |us| long. At 20 MSPS +sampling rate, this means each OFDM symbol contains 80 samples. The task is to +group the incoming streaming of samples into 80-sample OFDM symbols. This can be +achieved using the long preamble following the short preamble. + +.. _fig_training: +.. figure:: /images/training.png + :align: center + + 802.11 OFDM Packet Structure (Fig 18-4 in 802.11-2012 Std) + +As shown in :numref:`fig_training`, the long preamble duration is 8 |us| (160 +samples), and contains two identical long training sequence (LTS), 64 samples each. +The LTS is known and we can use `matched filter +`_ to find it. + +The match *score* at sample :math:`i` can be calculated as follows. + +.. math:: + :label: eq_matched + + Y[i] = \sum_{k=0}^{63}(S[i+k]\overline{H[63-k]}) + +where :math:`H` is the 64 sample known LTS in time domain, and can be found in +Table L-6 in :download:`802.11-2012 std ` (index 64 to +127). A numpy readable file of the LTS (64 samples) can be found :download:`here +`, and can be read like this: + +.. code-block:: python + + >>> import numpy as np + >>> lts = np.loadtxt('lts.txt').view(complex) + +.. _fig_lts: +.. figure:: /images/lts.png + :align: center + + Long Preamble and Matched Filter Result + +To plot :numref:`fig_lts`, load the data file (see :ref:`sec_sample`), then: + +.. code-block:: python + + # in scripts/decode.py + import decode + import numpy as np + from matplotlib import pyplot as plt + + fig, ax = plt.subplots(nrows=2, ncols=1, sharex=True) + ax[0].plot([c.real for c in samples][:500]) + # lts is from the above code snippet + ax[1].plot([abs(c) for c in np.convolve(samples, lts, mode='same')][:500], '-ro') + plt.show() + + + +:numref:`fig_lts` shows the long preamble samples and also the result of matched +filter. We can clearly see two spikes corresponding the two LTS in long +preamble. And the spike width is only 1 sample which shows exactly the beginning +of each sequence. Suppose the sample index if the first spike is :math:`N`, then +the 160 sample long preamble starts at sample :math:`N-33`. + +This all seems nice and dandy, but as it comes to Verilog implementation, we +have to make a few compromises. + +First, from :eq:`eq_matched` we can see for each sample, we need to perform 64 +complex number multiplications, which would consume a lot FPGA resources. +Therefore, we need to reduce the matched filter size. The idea is to only use +a portion instead of all the LTS samples. + +.. _fig_match_size: +.. figure:: /images/match_size.png + :align: center + + Matched Filter with Various Size (8, 16, 32, 64) + +:numref:`fig_match_size` can be plotted as: + +.. code-block:: python + + lp = decode.LONG_PREAMBLE + + fig, ax = plt.subplots(nrows=5, ncols=1, sharex=True) + ax[0].plot([c.real for c in lp]) + ax[1].plot([abs(c) for c in np.convolve(lp, lts[:8], mode='same')], '-ro') + ax[2].plot([abs(c) for c in np.convolve(lp, lts[:16], mode='same')], '-ro') + ax[3].plot([abs(c) for c in np.convolve(lp, lts[:32], mode='same')], '-ro'); + ax[4].plot([abs(c) for c in np.convolve(lp, lts, mode='same')], '-ro') + plt.show() + +:numref:`fig_match_size` shows the long preamble (160 samples) as well as +matched filter with different size. It can be seen that using the first 16 +samples of LTS is good enough to exhibit two narrow spikes. Therefore, |project| +use matched filter of size 16 for symbol alignment. And the first sample of the +long preamble starts at :math:`N_{16}-57`, where :math:`N_{16}` is the index of the +first spike when the filter size is 16 (:math:`N_{32}-49` when filter size is +32). diff --git a/docs/source/verilog.rst b/docs/source/verilog.rst index 5a32d78..2ac2282 100644 --- a/docs/source/verilog.rst +++ b/docs/source/verilog.rst @@ -3,13 +3,13 @@ Verilog Hacks Because of the limited capability of FPGA computation, compromises often need to made in the actual Verilog implementation. The most used techniques include -quantization and look up table. In OpenOFDM, these approximations are used. +quantization and look up table. In |project|, these approximations are used. Magnitude Estimation -------------------- -**Module**: ``complex_to_mag.v`` +**Module**: :file:`complex_to_mag.v` In the ``sync_short`` module, we need to calculate the magnitude of the ``prod_avg``, whose real and imagine part are both 32-bits. To avoid 32-bit @@ -39,11 +39,11 @@ magnitude is calculated. Phase Estimation ---------------- -**Module**:: ``phase.v`` +**Module**:: :file:`phase.v` When correcting the frequency offset, we need to estimate the phase of a complex number. The *right* way of doing this is probably using the `CORDIC -`_ algorithm. In OpenOFDM, we use look up +`_ algorithm. In |project|, we use look up table. More specifically, we calculate the phase using the :math:`arctan` function. @@ -91,9 +91,9 @@ This :math:`arctan` look up table is generated using the Note that we also scale up the :math:`arctan` values to distinguish adjacent -values. This also systematically scale up :math:`\pi` in OpenOFDM. In fact, +values. This also systematically scale up :math:`\pi` in |project|. In fact, :math:`\pi` is defined as :math:`1608=int(\pi*512)` in -``verilog/common_params.v``. +:file:`verilog/common_params.v`. The generated lookup table is stored in the ``verilog/atan_lut.coe`` file (see `COE File Syntax @@ -101,4 +101,4 @@ file (see `COE File Syntax Refer to `this guide `_ on how to create a look up table in Xilinx ISE. The generated module is stored in -``verilog/coregen/atan_lut.v``. +:file:`verilog/coregen/atan_lut.v`.