This is part of my work to implement a GNSS receiver in real time (see GNSSReceiver.jl).
One crucial part is the code generation that functions as a matched filter.
The code is binary and can be modeled as
code = Int32.(rand((-1, 1), 1023)) # GPS L1 has a length of 1023 GPS L5 has a length of 10230
It could in theory also be stored in a more compact form like BitArray or BitIntegers, but I wasn’t able to increase performance for this format.
The code needs to be sampled with the current code frequency and sampling frequency.
I came up with two implementations that have roughly the same performance:
function generate_code1!(sampled_code, phases, code, code_frequency, sampling_frequency)
sampling_frequency_i32 = Base.SignedMultiplicativeInverse(floor(Int32, sampling_frequency))
code_length = Int32(1023)
code_frequency_i32 = floor(Int32, code_frequency)
@inbounds for (idx, i) = enumerate(Int32(0):Int32(length(sampled_code)-1))
phases[idx] = mod(div(code_frequency_i32 * i, sampling_frequency_i32), code_length)
end
@inbounds for i = 1:length(sampled_code)
sampled_code[i] = code[phases[i] + 1]
end
end
If you have never heard about SignedMultiplicativeInverse
you can read about it here. It is a nice way to implement integer division.
Here is the second implementation:
using FixedPointNumbers
function generate_code2!(sampled_code, code, code_frequency, sampling_frequency)
FP = Fixed{Int, 53}
code_length_fp = FP(length(code))
delta_fp = FP(code_frequency / sampling_frequency)
phase_fp = FP(0)
@inbounds for i = 1:length(sampled_code)
sampled_code[i] = code[floor(Int,phase_fp) + 1]
phase_fp += delta_fp
phase_fp -= (phase_fp >= code_length_fp) * code_length_fp
end
end
The second implementation might drift for very large sampled_code
arrays because it is based on a delta, but accuracy isn’t my main concern.
In both cases I used an integer implementation instead of float because the conversion from float to integer floor(Int, phase)
seemed to be too inefficient.
Here is the code to run both functions:
using BenchmarkTools
code = Int32.(rand((-1, 1), 1023)) # GPS L1 has a length of 1023 GPS L5 has a length of 10230
num_samples = 2000
sampled_code1 = zeros(Int32, num_samples)
sampled_code2 = zeros(Int32, num_samples)
phases = zeros(Int32, num_samples)
@btime generate_code1!($sampled_code1, $phases, $code, $1023e3, $5e6)
#1.488 μs (0 allocations: 0 bytes)
@btime generate_code2!($sampled_code2, $code, $1023e3, $5e6)
#1.334 μs (0 allocations: 0 bytes)
Can we get any faster than this?
I wonder because I almost see no vectorized instructions in @code_native