4.8 KiB
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 , these approximations are used.
Magnitude Estimation
- Module:
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 ⟨I, Q⟩
is estimated as:
M ≈ α * max(|I|,|Q|) + β * min(|I|,|Q|)
And we set α = 1 and β = 0.25 so that only simple bit-shift is needed.
Waveform of
complex_to_mag
Module
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.
Phase Estimation
- Module:
phase.v
- Input:
i (32), q (32)
- Output:
phase (32)
- Note: The returned phase is scaled up by 512 (i.e., int(θ*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 , we use look up table.
More specifically, we calculate the phase using the arctan function.
$$\theta = \angle(\langle I, Q\rangle) = arctan(\frac{Q}{I})$$
The overall steps are:
- Project the complex number to the [0,π/4] range, so that the tan(θ) range is [0,1].
- Calculate arctan (division required)
- Looking up the quantized arctan table
- Project the phase back to the [ − π, π) range
Here we use both quantization and look up table techniques.
Step 1 can be achieved by this transformation:
⟨I, Q⟩ → ⟨max(|I|,|Q|), min(|I|,|Q|)⟩
In the lookup table used in step 3, we use int(tan(θ)*256) as the key, which effectively maps the [0.0,1.0] range of tan function to the integer range of [0,256]. In other words, we quantize the [0,π/4] quadrant into 256 slices.
This arctan
look up table is generated using the
scripts/gen_atan_lut.py
script. The core logic is as
follows:
= 2**8
SIZE = SIZE*2
SCALE = []
data for i in range(SIZE):
= float(i)/SIZE
key = int(round(math.atan(key)*SCALE))
val data.append(val)
Note that we also scale up the arctan
values to distinguish adjacent values. This also systematically scale up
π in . In fact, π is defined as 1608 = int(π*512)
in 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 verilog/coregen/atan_lut.v
.
Rotation
- Module:
/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 C = I + jQ
by θ degree, we can multiply
it by ejθ, as
shown in eq_rot
.
C′ = (I+jQ) × (cos(θ)+jsin(θ))
Again, this can be done using the CORDIC algorithm. But similar to
sec_phase
, we use the
look up table.
Quadrant in I/Q Plane
As shown in fig_quadrant
, we split the I/Q plane into 8
quadrants, π/4 each. To avoid
storing nearly duplicate entries in the table, we first map the phase to
be rotated ([−π,π])
into the [0,π/4] range. Next,
since the incoming phase is scaled up by 512, each quadrant is further
split into 402 = int(π/4*512)
sectors. And the cos (θ) and
sin (θ) values (scaled up by
2048) are stored in the look up table. The table is generated by the
scripts/gen_rot_lut.py
.