mirror of
https://github.com/jhshi/openofdm.git
synced 2024-12-20 06:08:11 +00:00
152 lines
4.8 KiB
ReStructuredText
152 lines
4.8 KiB
ReStructuredText
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 |project|, these approximations are used.
|
|
|
|
|
|
Magnitude Estimation
|
|
--------------------
|
|
|
|
- **Module**: :file:`complex_to_mag.v`
|
|
- **Input**: ``i (32), q (32)``
|
|
- **Output**: ``mag (32)``
|
|
|
|
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
|
|
multiplication, we use the `Magnitude Estimator Trick from DSP Guru
|
|
<https://dspguru.com/dsp/tricks/magnitude-estimator/>`_. In particular, the
|
|
magnitude of complex number :math:`\langle I, Q\rangle` is estimated as:
|
|
|
|
.. math::
|
|
|
|
M \approx \alpha*max(|I|, |Q|) + \beta*min(|I|, |Q|)
|
|
|
|
And we set :math:`\alpha = 1` and :math:`\beta = 0.25` so that only simple
|
|
bit-shift is needed.
|
|
|
|
.. _fig_complex_to_mag_wave:
|
|
.. figure:: /images/complex_to_mag_wave.png
|
|
:align: center
|
|
|
|
Waveform of ``complex_to_mag`` Module
|
|
|
|
:numref:`fig_complex_to_mag_wave` shows the waveform of the ``complex_to_mag``
|
|
module. In the first clock cycle, we calculate ``abs_i`` and ``abs_q``. In the
|
|
second cycle, ``max`` and ``min`` are determined. In the final cycle, the
|
|
magnitude is calculated.
|
|
|
|
|
|
.. _sec_phase:
|
|
|
|
Phase Estimation
|
|
----------------
|
|
|
|
- **Module**: :file:`phase.v`
|
|
- **Input**: ``i (32), q (32)``
|
|
- **Output**: ``phase (32)``
|
|
- **Note**: The returned phase is scaled up by 512 (i.e., :math:`int(\theta *512)`)
|
|
|
|
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
|
|
<https://dspguru.com/dsp/faqs/cordic/>`_ algorithm. In |project|, we use look up
|
|
table.
|
|
|
|
More specifically, we calculate the phase using the :math:`arctan` function.
|
|
|
|
|
|
.. math::
|
|
|
|
\theta = \angle(\langle I, Q\rangle) = arctan(\frac{Q}{I})
|
|
|
|
The overall steps are:
|
|
|
|
1. Project the complex number to the :math:`[0, \pi/4]` range, so that the
|
|
:math:`tan(\theta)` range is :math:`[0, 1]`.
|
|
#. Calculate :math:`arctan` (division required)
|
|
#. Looking up the quantized :math:`arctan` table
|
|
#. Project the phase back to the :math:`[-\pi, \pi)` range
|
|
|
|
Here we use both quantization and look up table techniques.
|
|
|
|
Step 1 can be achieved by this transformation:
|
|
|
|
.. math::
|
|
|
|
\langle I, Q\rangle \rightarrow \langle max(|I|, |Q|), min(|I|, |Q|)\rangle
|
|
|
|
|
|
In the lookup table used in step 3, we use :math:`int(tan(\theta)*256)` as the
|
|
key, which effectively maps the :math:`[0.0, 1.0]` range of :math:`tan` function
|
|
to the integer range of :math:`[0, 256]`. In other words, we quantize the
|
|
:math:`[0, \pi/4]` quadrant into 256 slices.
|
|
|
|
This :math:`arctan` look up table is generated using the
|
|
``scripts/gen_atan_lut.py`` script. The core logic is as follows:
|
|
|
|
.. code-block:: python
|
|
:linenos:
|
|
|
|
SIZE = 2**8
|
|
SCALE = SIZE*2
|
|
data = []
|
|
for i in range(SIZE):
|
|
key = float(i)/SIZE
|
|
val = int(round(math.atan(key)*SCALE))
|
|
data.append(val)
|
|
|
|
|
|
Note that we also scale up the :math:`arctan` values to distinguish adjacent
|
|
values. This also systematically scale up :math:`\pi` in |project|. In fact,
|
|
:math:`\pi` is defined as :math:`1608=int(\pi*512)` in
|
|
:file:`verilog/common_params.v`.
|
|
|
|
The generated lookup table is stored in the ``verilog/atan_lut.coe``
|
|
file (see `COE File Syntax
|
|
<https://www.xilinx.com/support/documentation/sw_manuals/xilinx11/cgn_r_coe_file_syntax.htm>`_).
|
|
Refer to `this guide
|
|
<https://www.xilinx.com/itp/xilinx10/isehelp/cgn_p_memed_single_block.htm>`_ on
|
|
how to create a look up table in Xilinx ISE. The generated module is stored in
|
|
:file:`verilog/coregen/atan_lut.v`.
|
|
|
|
|
|
|
|
.. _rotate:
|
|
|
|
Rotation
|
|
--------
|
|
|
|
- **Module**: :file:`/verilog/rotate.v`
|
|
- **Input**: ``i (16), q (16), phase (32)``
|
|
- **Output**: ``out_i (16), out_q (16)``
|
|
- **Note**: The input phase is assumed to be scaled up by 512.
|
|
|
|
To rotate a complex number :math:`C=I+jQ` by :math:`\theta` degree, we can
|
|
multiply it by :math:`e^{j\theta}`, as shown in :eq:`eq_rot`.
|
|
|
|
.. math::
|
|
:label: eq_rot
|
|
|
|
C' = (I+jQ)\times(\cos(\theta)+j\sin(\theta))
|
|
|
|
Again, this can be done using the CORDIC algorithm. But similar to
|
|
:ref:`sec_phase`, we use the look up table.
|
|
|
|
|
|
.. _fig_quadrant:
|
|
.. figure:: /images/quadrant.png
|
|
:align: center
|
|
:scale: 60%
|
|
|
|
Quadrant in I/Q Plane
|
|
|
|
As shown in :numref:`fig_quadrant`, we split the I/Q plane into 8 quadrants,
|
|
:math:`\pi/4` each. To avoid storing nearly duplicate entries in the table, we
|
|
first map the phase to be rotated (:math:`[-\pi, \pi]`) into the :math:`[0,
|
|
\pi/4]` range. Next, since the incoming phase is scaled up by 512, each quadrant
|
|
is further split into :math:`402=int(\pi/4*512)` sectors. And the
|
|
:math:`\cos(\theta)` and :math:`\sin(\theta)` values (scaled up by 2048) are
|
|
stored in the look up table. The table is generated by the
|
|
:file:`scripts/gen_rot_lut.py`.
|