Saturday, April 26, 2014

Modelsim and bladeRF go happy together (hopefully)

Introduction and excuses

Busy period last few weeks. Even a retired man sometimes has obligations. And other hobbies perhaps.
I did some programming with SpaceClaim, what a wonderful 3D software! The keyboard of my laptop got some Armagnac, what a waste! Now I use an external keyboard… I spent some weeks in a caravan with my wife, of course I found some time to fiddle with my bladeRF! And I had an ebook (well, actually a pdf file) about VHDL.
But I found some time to study more VHDL, surf the web and think about my bladeRF project. Because the process is more important than my goal/end result I sometimes take sideways instead of the main road to success. An email from Brian made me thinking…

Simulate/debug

For a simple project you just write some code, try it, find some errors, correct it and finally it is OK. For a little bit more complicated projects this does not work anymore. You need tools. You need an editor that helps you with the syntax of your program, whether it is written in C, python or VHDL. Then you need a debugger/simulator. I don’t like debuggers. I read the error codes, get my aha-erlebnis, correct my errors (well, some, not all) and recompile. Sometimes a lot of cycles in this process.
Many times I saw Mark, a former colleague, use a debugger. He always finds in a very short time all the errors in a program. In the time that I need to edit and compile he runs a debugger/simulator and finds all or most errors.
Brian is right: you need to use Modelsim to simulate your design. Not only to find errors but also to better understand your very own program!

Modelsim

So I decided to learn how to use Modelsim. I read the tutorial and did some experiments. It seems that the tutorial describes a different version than I have in my computer. I need a different method of working with Modelsim. Instead of painfully following a script I will try to describe in global terms what and why in the hope that this leads to a better way of understanding and doing simulation.
In this blog I will try to describe what I learnt so far, in the next blog I hope to show some results.

Modelsim

Alterras tutorial uses two files:

C:\altera\work\counter.vhd
C:\altera\work\tcounter.vhd

Have a look at these files.

Counter.vhd

Counter.vhd is a regular vhdl-implementation of a counter. This file looks a bit complicated/daunting  because it is a universal counter. Furthermore they use a function increment. But this is a nice example of a universal VHDL-implementation of a counter.

This counter, counter.vhd, is simulated.
For that, you need an input for the signals clk and reset from the port specification and in the simulation the count will be altered by the clk and reset-signals. In the simulation you should be able to see all these signals.

This counter becomes the DUT, Device Under Test.

How would you test this counter on your lab bench? Well, I would take a button for the reset-signal and a clock-generator connected at the clk-input of the counter.
Then I would connect some display device at the count output of the counter.

I would connect a power supply, start my clock generator and press the reset button. Then I would have a look at the counter. If all is well, you would see an increasing count value. I.e. the higher order bits of count should change less often than the lower bits.

This is exactly what Modelsim will do. You have to connect the DUT to a test-setup. What is a test-setup? A vhdl-file that tells the setup/connections at your workbench and the settings of the signal-generator!

tcounter.vhd

Now have a look at tcounter.vhd

Actually it is a simple file. It describes the port-specification of the DUT, which is our counter counter.vhd. Then incorporates it and feeds it with a clock and a stimulus.

Clock is a simple process that generates a clock-signal that is 10 ns high, 10 ns low. One cycle is 20 ns which makes it into a 50 MHz clock. Simple eh?

Stimulus is a  process that generates the reset signal and then waits forever.

If you start Modelsim and feed it with these two VHDL-files you have your simulation. But how exactly to do that?
To be honest, I don’t  know yet, but I will find out soon…

Stay tuned!


Conclusion

The email from Brian as a response to my previous message is a perfect example of help from the Internet. Brian brought me back to the main road by a very concise and very good explanation of my problems. He did not solve all my problems by the way and that is good! 
I am gonna play around with Modelsim sometime till I know enough to use this product for all my future  VHDL-problems. In the meantime I will try to describe what I did in the hope that others will have less problems than I have/had.

Hope this helps

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.