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 `_. 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 `_ 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 `_). Refer to `this guide `_ 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`.