Thursday, March 27, 2014

First VHDL-attempts to remove DC from BladeRF-outputs

First VHDL-attempts to remove DC from BladeRF-outputs


Well, subject says it all.

Last few weeks I did study VHDL a lot and came up with the following change of the lms6002d.vhdl piece:

library ieee ;
    use ieee.std_logic_1164.all ;
    use ieee.numeric_std.all ;

-- modified to remove DC-component
-- March-2014 KdG
           
entity lms6002d is
  port (
    -- RX Controls
    rx_clock            :   in      std_logic ;
    rx_reset            :   in      std_logic ;
    rx_enable           :   in      std_logic ;

    -- RX Interface with LMS6002D
    rx_lms_data         :   in      signed(11 downto 0) ;
    rx_lms_iq_sel       :   in      std_logic ;
    rx_lms_enable       :   buffer  std_logic ;

    -- RX Sample Interface
    rx_sample_i         :   buffer  signed(11 downto 0) ;
    rx_sample_q         :   buffer  signed(11 downto 0) ;
    rx_sample_valid     :   buffer  std_logic ;

    -- TX Controls
    tx_clock            :   in      std_logic ;
    tx_reset            :   in      std_logic ;
    tx_enable           :   in      std_logic ;

    -- TX Sample Interface
    tx_sample_i         :   in      signed(11 downto 0) ;
    tx_sample_q         :   in      signed(11 downto 0) ;
    tx_sample_valid     :   in      std_logic ;

    -- TX Interface to the LMS6002D
    tx_lms_data         :   buffer  signed(11 downto 0) ;
    tx_lms_iq_sel       :   buffer  std_logic ;
    tx_lms_enable       :   buffer  std_logic
  ) ;
end entity ;

architecture arch of lms6002d is

signal rx_average_i, rx_average_q : signed(11 downto 0);
signal accum_i : signed(18 downto 0) ;
signal accum_q : signed(18 downto 0) ;

begin

    -------------
    -- Receive --
    -------------
    rx_sample : process(rx_clock, rx_reset)
    begin
        if( rx_reset = '1' ) then
            rx_sample_i <= (others =>'0') ;
            rx_sample_q <= (others =>'0') ;
                     rx_average_i <= (others => '0'); -- KdG
                     rx_average_q <= (others => '0');     -- KdG              
            rx_sample_valid <= '0' ;
            rx_lms_enable <= '0' ;
        elsif( rising_edge( rx_clock ) ) then
            if( rx_lms_iq_sel = '0' ) then
                rx_lms_enable <= rx_enable ;
            end if ;

            rx_sample_valid <= '0' ;
            if( rx_lms_enable = '1' ) then
                if(rx_lms_iq_sel = '1' ) then
                    -- rx_sample_i <= rx_lms_data;
                               rx_sample_i <= rx_lms_data - rx_average_i; -- KdG
                else
                    -- rx_sample_q <= rx_lms_data;
                                 rx_sample_q <= rx_lms_data - rx_average_q ; -- KdG
                    rx_sample_valid <= '1' ;
                end if ;
            end if ;
        end if ;
    end process ;

    --------------
    -- Transmit --
    --------------
    tx_sample : process(tx_clock, tx_reset)
        variable tx_q_reg   :   signed(11 downto 0) ;
    begin
        if( tx_reset = '1' ) then
            tx_lms_data <= (others =>'0') ;
            tx_lms_iq_sel <= '0' ;
            tx_lms_enable <= '0' ;
            tx_q_reg := (others =>'0') ;
        elsif( rising_edge( tx_clock ) ) then
            if( tx_lms_iq_sel = '0' ) then
                tx_lms_enable <= tx_enable ;
            end if ;

            if( tx_sample_valid = '1' ) then
                tx_lms_data <= tx_sample_i ;
                tx_q_reg := tx_sample_q ;
                tx_lms_iq_sel <= '0' ;
            elsif( tx_lms_enable = '1' ) then
                tx_lms_data <= tx_q_reg ;
                tx_lms_iq_sel <= '1' ;
            else
                tx_lms_data <= (others =>'0') ;
                tx_lms_iq_sel <= '0' ;
            end if ;
        end if ;
    end process ;
      
      average: process(rx_sample_valid)
          variable count : integer ;
    begin
          if( rising_edge( rx_sample_valid ) ) then
                if(rx_lms_iq_sel = '1' ) then
                       accum_i <= accum_i + rx_lms_data;
                     else
                    accum_q <= accum_q + rx_lms_data;
                     end if;  
                count := count + 1;
                     if ( count > 128 ) then
                         count := 0;
                           rx_average_i(11 downto 0) <= accum_i (18 downto 7);
                           rx_average_q(11 downto 0) <= accum_q (18 downto 7);
                           accum_i <= (others =>'0') ;
                           accum_q <= (others =>'0') ;
                     end if;
             end if;
      end process average;
      
end architecture ;

I declared some signals and calculate the average in a separate process.

I get some errors from Quartus:

Error (10028): Can't resolve multiple constant drivers for net "rx_average_i[11]" at lms6002d.vhd(114)
Error (10029): Constant driver at lms6002d.vhd(53)
Error (10028): Can't resolve multiple constant drivers for net "rx_average_i[10]" at lms6002d.vhd(114)
Error (10028): Can't resolve multiple constant drivers for net "rx_average_i[9]" at lms6002d.vhd(114)
Error (10028): Can't resolve multiple constant drivers for net "rx_average_i[8]" at lms6002d.vhd(114)
Error (10028): Can't resolve multiple constant drivers for net "rx_average_i[7]" at lms6002d.vhd(114)
Error (10028): Can't resolve multiple constant drivers for net "rx_average_i[6]" at lms6002d.vhd(114)
Error (10028): Can't resolve multiple constant drivers for net "rx_average_i[5]" at lms6002d.vhd(114)
Error (10028): Can't resolve multiple constant drivers for net "rx_average_i[4]" at lms6002d.vhd(114)
Error (10028): Can't resolve multiple constant drivers for net "rx_average_i[3]" at lms6002d.vhd(114)
Error (10028): Can't resolve multiple constant drivers for net "rx_average_i[2]" at lms6002d.vhd(114)
Error (10028): Can't resolve multiple constant drivers for net "rx_average_i[1]" at lms6002d.vhd(114)
Error (10028): Can't resolve multiple constant drivers for net "rx_average_i[0]" at lms6002d.vhd(114)
Error (10028): Can't resolve multiple constant drivers for net "rx_average_q[11]" at lms6002d.vhd(114)
Error (10028): Can't resolve multiple constant drivers for net "rx_average_q[10]" at lms6002d.vhd(114)
Error (10028): Can't resolve multiple constant drivers for net "rx_average_q[9]" at lms6002d.vhd(114)
Error (10028): Can't resolve multiple constant drivers for net "rx_average_q[8]" at lms6002d.vhd(114)
Error (10028): Can't resolve multiple constant drivers for net "rx_average_q[7]" at lms6002d.vhd(114)
Error (10028): Can't resolve multiple constant drivers for net "rx_average_q[6]" at lms6002d.vhd(114)

Thusfar I do not understand this error-messages.

Because it was a long time ago already that I posted something on my blog, this is a kind of “I am still alive message”.

I will dig into this problem, but I would LOVE to get some hints from VHDL-gurus!

Next time I hope to come up with a working VHDl-solution.

Wednesday, February 26, 2014

Removing the DC component on the fly

Correlator

For the GPS-frontend I need a correlator. The ADC-output from the LMS is 12 bits. That is a lot of bits and makes the correlator huge. Therefore I want to reduce the ADC-output to a single analogue value: -1 or +1.
This reduction is not too difficult to put in an FPGA. All you have to do is:
If sample > average then output := 1 else output := -1  (or perhaps 0 in the latter case?)

0/1 or -1/+1 ?

Multiply
+1 * +1 = +1
+1 * -1 = -1
-1 * +1 = -1
-1 * -1 = +1

This looks like an EXOR:
1 XOR 1 = 0
1 XOR 0 = 1
0 XOR 1 = 1
0 XOR 0 = 0

Average or DC-component

If you have a file with samples, calculating the average is very simple. Put all samples in an accumulator and divide the result by the number of samples. But waiting for a zillion of samples is not necessary. If you average the current 100 samples you have a fairly accurate estimation of the average. The average, by the way, is the DC-component. With an FFT, or DFT for that matter, X[0] is the DC-component, the average. But that is a little bit overdone. How many samples do you need for a reasonable estimate of the average? I have to divide the accumulated sum of the samples by the number of samples. If the number of samples is a power of 2 then division is simply removing some zeroes or shifting to the right!

So I need two simple parallel processes:

reducer: Process()
    If sample > average
    then output := 1
    else output := -1
end reducer

calc average: Process()
accumaverage := 0
counter := 0
    accumaverage := accumaverage + sample
    counter := counter + 1
    if counter == 128
    then
        average := accumaverage/128
        counter := 0
        accumaverage := 0
end calc average

This should be done for I and for Q.  
                                                                                                                                                                  What number system is used by the ADC? Two’s complement
How many samples: 128, more? less?

If the average is reasonable constant, this is a good estimation if you have ‘enough’ samples. If the average fluctuates a bit, you want as few samples as possible. How few? Furthermore you want the estimation of the average asap, so fewer samples is better.

Simulations in python

# binaryAverage01.py
# Feb-2014 Kees de Groot
#
# remove average (DC-component)
# by accumulating 128 samples and divide by 128
# perform some experiments/simulations
   
import pylab as pl
import numpy as np
import math
import sys

######################################################################
############# parameter section ######################################
######################################################################

path = '/Temp/'
filename = 'DDOUD.csv'
f = open(path + filename, 'rb')
print "filename = ", path + filename
skip = 0 # 10000 # there is a discontinuity in the file
            # so skip half of it
print "skip ", skip, "samples of input-data from datafile"

###########################################
#############  main loop ##################
###########################################

dataI = []
dataQ = []
averI = 0.0
averQ = 0.0

# read (I,Q) from bladeRF file
n = 1024
i = 0
for line in f:
    skip = skip - 1
    if skip > 0:
        continue
    list = line.split(',')
    Q = int(list[0])
    averQ += Q
    I = int(list[1])
    averI += I
    dataI.append(I)
    dataQ.append(Q)
    i += 1
    if i >= n:
        break
print "read ", n, " samples from file"
print "averI = ", averI
print "averQ = ", averQ
averI = averI/n
averQ = averQ/n
print "averI/n = ", averI
print "averQ/n = ", averQ

pl.figure(1)
pl.title("raw I/Q")
pl.plot(dataI, dataQ, 'ro')
##pl.figure(2)
##pl.title("dataI")
##pl.plot(dataI)
##pl.figure(3)
##pl.title("dataQ")
##pl.plot(dataQ)
##pl.show()

# sys.exit('debug')

# remove DC-component
dataC = []
for i in range(len(dataI)):
    dataI[i] -= averI
    dataQ[i] -= averQ
       
pl.figure(4)
pl.title("I/Q, DC-component removed")
pl.plot(dataI, dataQ, 'ro')
pl.show()

sys.exit('debug')

With output

Python 2.7.3 (default, Apr 10 2012, 23:31:26) [MSC v.1500 32 bit (Intel)] on win32
Type "copyright", "credits" or "license()" for more information.
>>> ================================ RESTART ================================
>>>
filename =  /Temp/DDOUD.csv
skip  0 samples of input-data from datafile
read  1024  samples from file
averI =  518907.0
averQ =  628875.0
averI/n =  506.745117188
averQ/n =  614.135742188


The center of the cluster datapoints is at about (500,600)


With the DC component removed, the center is at (0, 0).

Moving average


# binaryAverage02.py
# Feb-2014 Kees de Groot
#
# remove average (DC-component)
# by accumulating 128 samples and divide by 128
# perform some experiments/simulations
   
import pylab as pl
import numpy as np
import math
import sys

######################################################################
############# parameter section ######################################
######################################################################

path = '/Temp/'
filename = 'DDOUD.csv'
f = open(path + filename, 'rb')
print "filename = ", path + filename
skip = 0 # 10000 # there is a discontinuity in the file
            # so skip half of it
print "skip ", skip, "samples of input-data from datafile"

###########################################
#############  main loop ##################
###########################################

n = 1024 # number of samples to process
countermax = 128 # number of samples to average
counter = 0
dataI = []
dataQ = []
averI = 0.0
averQ = 0.0
accQ = 0.0
accI = 0.0

# read (I,Q) from bladeRF file
i = 0
for line in f:
    skip = skip - 1
    if skip > 0:
        continue
    list = line.split(',')
    Q = int(list[0])
    accQ += Q
    I = int(list[1])
    accI += I
    dataI.append(I - averI)
    dataQ.append(Q - averQ)
    i += 1
    if i >= n:
        break
    counter += 1
    if counter == countermax:
        averQ = accQ / countermax
        accQ = 0
        averI = accI / countermax
        accI = 0
        counter = 0
       
print "read ", n, " samples from file"
print "averI = ", averI
print "averQ = ", averQ
averI = averI/n
averQ = averQ/n
print "averI/n = ", averI
print "averQ/n = ", averQ

pl.figure(1)
pl.title("raw I/Q")
pl.plot(dataI, dataQ, 'ro')
##pl.figure(2)
##pl.title("dataI")
##pl.plot(dataI)
##pl.figure(3)
##pl.title("dataQ")
##pl.plot(dataQ)
pl.show()

sys.exit('debug')

# remove DC-component
dataC = []
for i in range(len(dataI)):
    dataI[i] -= averI
    dataQ[i] -= averQ
       
pl.figure(4)
pl.title("I/Q, DC-component removed")
pl.plot(dataI, dataQ, 'ro')
pl.show()

sys.exit('debug')


With output

Python 2.7.3 (default, Apr 10 2012, 23:31:26) [MSC v.1500 32 bit (Intel)] on win32
Type "copyright", "credits" or "license()" for more information.
>>> ================================ RESTART ================================
>>>
filename =  /Temp/DDOUD.csv
skip  0 samples of input-data from datafile
read  1024  samples from file
averI =  510
averQ =  621
averI/n =  0
averQ/n =  0



The first 128 points have the original DC-component and are topright in the figure.
The remaining sample have the DC-component removed, so are upperleft, around (0,0)

Plot of moving average


# binaryAverage03.py
# Feb-2014 Kees de Groot
#
# remove average (DC-component)
# by accumulating 128 samples and divide by 128
# perform some experiments/simulations
# plot average
   
import pylab as pl
import numpy as np
import math
import sys

######################################################################
############# parameter section ######################################
######################################################################

path = '/Temp/'
filename = 'DDOUD.csv'
f = open(path + filename, 'rb')
print "filename = ", path + filename
skip = 0 # 10000 # there is a discontinuity in the file
            # so skip half of it
print "skip ", skip, "samples of input-data from datafile"

###########################################
#############  main loop ##################
###########################################

n = 1024*256 # number of samples to process
print "n = (number of samples to process)", n
countermax = 32 # number of samples to average
counter = 0
dataI = []
dataQ = []
averI = 0.0
averQ = 0.0
accQ = 0.0
accI = 0.0
averageI = []

# read (I,Q) from bladeRF file
i = 0
for line in f:
    skip = skip - 1
    if skip > 0:
        continue
    list = line.split(',')
    Q = int(list[0])
    accQ += Q
    I = int(list[1])
    accI += I
    dataI.append(I - averI)
    dataQ.append(Q - averQ)
    i += 1
    if i >= n:
        break
    counter += 1
    if counter == countermax:
        averQ = accQ / countermax
        accQ = 0
        averI = accI / countermax
        averageI.append(averI)
        accI = 0
        counter = 0
       
print "read ", n, " samples from file"
print "averI = ", averI
print "averQ = ", averQ
averI = averI/n
averQ = averQ/n
print "averI/n = ", averI
print "averQ/n = ", averQ

pl.figure(1)
pl.title("raw I/Q")
pl.plot(dataI, dataQ, 'ro')

pl.figure(2)
pl.title("average I")
pl.plot(averageI)
##pl.figure(3)
##pl.title("dataQ")
##pl.plot(dataQ)
pl.show()

sys.exit('debug')

# remove DC-component
dataC = []
for i in range(len(dataI)):
    dataI[i] -= averI
    dataQ[i] -= averQ
       
pl.figure(4)
pl.title("I/Q, DC-component removed")
pl.plot(dataI, dataQ, 'ro')
pl.show()

sys.exit('debug')

With output

>>> ================================ RESTART ================================
>>>
filename =  /Temp/DDOUD.csv
skip  0 samples of input-data from datafile
n = (number of samples to process) 262144
read  262144  samples from file
averI =  487
averQ =  597
averI/n =  0
averQ/n =  0



Ah, interesting. Why peaks?
Need some more experiments. At the moment enough interesting things for my blog…

Summary

With a python program I simulated the behaviour of a piece of VHDL-code that I want to try out in the next instalment of this blog. This looks promising but at the end I found some strange peaks in the plot that I have to understand.

As always: if you find errors, if you have suggestions, if you see what I don't see immediately, please comment.