mirror of
https://github.com/jhshi/openofdm.git
synced 2025-06-15 13:48:13 +00:00
phase lut
This commit is contained in:
@ -42,34 +42,63 @@ Phase Estimation
|
|||||||
**Module**:: ``phase.v``
|
**Module**:: ``phase.v``
|
||||||
|
|
||||||
When correcting the frequency offset, we need to estimate the phase of a complex
|
When correcting the frequency offset, we need to estimate the phase of a complex
|
||||||
number, which can be calculated using the :math:`arctan` function.
|
number. The *right* way of doing this is probably using the `CORDIC
|
||||||
|
<https://dspguru.com/dsp/faqs/cordic/>`_ algorithm. In OpenOFDM, we use look up
|
||||||
|
table.
|
||||||
|
|
||||||
|
More specifically, we calculate the phase using the :math:`arctan` function.
|
||||||
|
|
||||||
|
|
||||||
.. math::
|
.. math::
|
||||||
|
|
||||||
\angle(\langle I, Q\rangle) = arctan(\frac{Q}{I})
|
\theta = \angle(\langle I, Q\rangle) = arctan(\frac{Q}{I})
|
||||||
|
|
||||||
The overall steps are:
|
The overall steps are:
|
||||||
|
|
||||||
1. Project the complex number to the :math:`[0, \pi/4]` range.
|
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)
|
#. Calculate :math:`arctan` (division required)
|
||||||
#. Looking up the quantized :math:`arctan` table
|
#. Looking up the quantized :math:`arctan` table
|
||||||
#. Project the phase back to the :math:`[-\pi, \pi)` range
|
#. Project the phase back to the :math:`[-\pi, \pi)` range
|
||||||
|
|
||||||
Here we use both quantization and look up table techniques.
|
Here we use both quantization and look up table techniques.
|
||||||
|
|
||||||
The first step can be achieved by this transformation:
|
Step 1 can be achieved by this transformation:
|
||||||
|
|
||||||
.. math::
|
.. math::
|
||||||
|
|
||||||
\langle I, Q\rangle \rightarrow \langle max(|I|, |Q|), min(|I|, |Q|)\rangle
|
\langle I, Q\rangle \rightarrow \langle max(|I|, |Q|), min(|I|, |Q|)\rangle
|
||||||
|
|
||||||
|
|
||||||
The *right* way to calculate :math:`arctan` is probably using the `CORDIC
|
In the lookup table used in step 3, we use :math:`int(tan(\theta)*256)` as the
|
||||||
<https://dspguru.com/dsp/faqs/cordic/>`_ algorithm. However, this function is
|
key, which effectively maps the :math:`[0.0, 1.0]` range of :math:`tan` function
|
||||||
implemented using look up tables in OpenOFDM.
|
to the integer range of :math:`[0, 256]`. In other words, we quantize the
|
||||||
|
:math:`[0, \pi/4]` quadrant into 256 slices.
|
||||||
|
|
||||||
In the table, we use :math:`int(tan(\angle)*256)` as the key, which effective
|
This :math:`arctan` look up table is generated using the
|
||||||
map the :math:`[0.0, 1.0]` range of :math:`tan` function to the integer range of
|
``scripts/gen_atan_lut.py`` script. The core logic is as follows:
|
||||||
:math:`[0, 256]`. In other words, we quantize the :math:`[0, \pi/4]` quadrant
|
|
||||||
into 256 slices.
|
.. 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 OpenOFDM. In fact,
|
||||||
|
:math:`\pi` is defined as :math:`1608=int(\pi*512)` in
|
||||||
|
``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
|
||||||
|
``verilog/coregen/atan_lut.v``.
|
||||||
|
@ -225,6 +225,7 @@ class ChannelEstimator(object):
|
|||||||
prod_sum += prod
|
prod_sum += prod
|
||||||
beta = cmath.phase(prod_sum)
|
beta = cmath.phase(prod_sum)
|
||||||
print "[PILOT OFFSET] %f (%d)" % (beta, int(beta*PHASE_SCALE))
|
print "[PILOT OFFSET] %f (%d)" % (beta, int(beta*PHASE_SCALE))
|
||||||
|
# beta = 0
|
||||||
carriers = []
|
carriers = []
|
||||||
for c in self.subcarriers:
|
for c in self.subcarriers:
|
||||||
if c in PILOT_SUBCARRIES:
|
if c in PILOT_SUBCARRIES:
|
||||||
@ -269,8 +270,8 @@ class ChannelEstimator(object):
|
|||||||
|
|
||||||
coarse_offset = cmath.phase(sum([sts[i]*sts[i+16].conjugate()
|
coarse_offset = cmath.phase(sum([sts[i]*sts[i+16].conjugate()
|
||||||
for i in range(len(sts)-16)]))/16
|
for i in range(len(sts)-16)]))/16
|
||||||
coarse_offset = int(coarse_offset*256)/256.0
|
|
||||||
print '[COARSE OFFSET] %f (%d)' % (coarse_offset, int(coarse_offset*PHASE_SCALE))
|
print '[COARSE OFFSET] %f (%d)' % (coarse_offset, int(coarse_offset*PHASE_SCALE))
|
||||||
|
# coarse_offset = 0
|
||||||
|
|
||||||
# coarse correction
|
# coarse correction
|
||||||
lts = [c*cmath.exp(complex(0, n*coarse_offset))
|
lts = [c*cmath.exp(complex(0, n*coarse_offset))
|
||||||
@ -279,7 +280,7 @@ class ChannelEstimator(object):
|
|||||||
fine_offset = cmath.phase(sum([lts[i]*lts[i+64].conjugate()
|
fine_offset = cmath.phase(sum([lts[i]*lts[i+64].conjugate()
|
||||||
for i in range(len(lts)-64)]))/64
|
for i in range(len(lts)-64)]))/64
|
||||||
print '[FINE OFFSET] %f (%d)' % (fine_offset, int(fine_offset*PHASE_SCALE))
|
print '[FINE OFFSET] %f (%d)' % (fine_offset, int(fine_offset*PHASE_SCALE))
|
||||||
fine_offset = 0
|
# fine_offset = 0
|
||||||
|
|
||||||
self.lts_samples = [c*cmath.exp(complex(0, n*fine_offset))
|
self.lts_samples = [c*cmath.exp(complex(0, n*fine_offset))
|
||||||
for n, c in enumerate(lts)]
|
for n, c in enumerate(lts)]
|
||||||
@ -430,6 +431,7 @@ class Decoder(object):
|
|||||||
def decode_next(self, *args, **kwargs):
|
def decode_next(self, *args, **kwargs):
|
||||||
trigger = False
|
trigger = False
|
||||||
samples = []
|
samples = []
|
||||||
|
glbl_index = 0
|
||||||
while True:
|
while True:
|
||||||
chunk = array.array('h', self.fh.read(self.window))
|
chunk = array.array('h', self.fh.read(self.window))
|
||||||
chunk = [complex(i, q) for i, q in zip(chunk[::2], chunk[1::2])]
|
chunk = [complex(i, q) for i, q in zip(chunk[::2], chunk[1::2])]
|
||||||
@ -437,6 +439,7 @@ class Decoder(object):
|
|||||||
trigger = True
|
trigger = True
|
||||||
samples = []
|
samples = []
|
||||||
print "Power trigger at %d" % (self.count)
|
print "Power trigger at %d" % (self.count)
|
||||||
|
glbl_index = self.count
|
||||||
|
|
||||||
self.count += self.window
|
self.count += self.window
|
||||||
|
|
||||||
@ -448,6 +451,8 @@ class Decoder(object):
|
|||||||
if start is None:
|
if start is None:
|
||||||
trigger = False
|
trigger = False
|
||||||
else:
|
else:
|
||||||
|
print "Decoding packet starting from sample %d" %\
|
||||||
|
(glbl_index + start)
|
||||||
return self.decode(samples[start:], *args, **kwargs)
|
return self.decode(samples[start:], *args, **kwargs)
|
||||||
|
|
||||||
def find_pkt(self, samples):
|
def find_pkt(self, samples):
|
||||||
|
Reference in New Issue
Block a user