Saturday, May 3, 2014

ModelSim testbench is the perfect companion for BladeRF VHDL-development

ModelSim is awesome indeed!

I took the plunge and did the Altera tutorial that came with ModelSim. Look in the help-files of ModelSim and you'll find the tutorial.

Summary

I found a lot of ModelSim tutorials on the web, all for a different version of ModelSim. Then I decided to invest in reading/studying/doing the Altera-tutorial from the help-file of ModelSim. After one Sunday-afternoon I had the tutorial running. What a satisfaction! 
I often write down all the steps I perform in a Word-document, my electronic diary. I simply use <Ctrl><Alt><PrtScr> and paste the screenshots in my Word-diary.
Because the bladeRF LMS-source is too difficult to simulate for the moment, I am a newby in ModelSim, I decided to design in VHDL a simple datasource for a sine with a DC-offset. Later I will test my DC-remover on this datasource.

I was too lazy to type in 32 12-bit values in the case-statement, so used a python-program to do this.

I managed to write and debug the code using ModelSim. 
The whole struggle with ModelSim is in here but you might be wise and skip to the end for the final result.
I finally constructed the datasource.vhd and the testbench tdatasource.vhd

ModelSim Tutorials on the web:

1.       http://www.stanford.edu/class/ee183/handouts_win2003/Modelsim_short_tutorial.pdf
Stanford, Winter 2002-2003, Modelsim SE (Student Edition?), 14 pages
Tutorial with Verilog only
2.       http://bertrand.granado.free.fr/Sysprog/SysProg/Cours_files/modelsim_tut.pdf
Mentor Graphics Corporation, 2009, Software Version 6.5b, 82 pages
3.       ftp://ftp.altera.com/up/pub/Altera_Material/10.1/Tutorials/Using_ModelSim.pdf
Altera, Jan 2011, Altera Starter Edition 6.4.a Custom Altera Version, 30 pages
4.       http://www.ece.northwestern.edu/~ismail/courses/c92/ModelSim_Tutorial.pdf
Larbi Boughaleb, UT ECE,  ModelSim 5.7, 9 pages
7.       Mentor Graphics, 2008, Software Version 6.4a, 88 pages
8.       http://users.ece.cmu.edu/~kbiswas/modelsim/se_tutor.pdf
Mentor Graphics Corporation, Nov 2004, Version 6.0b, 178 pages
9.       modelsim_tut.pdf from help-file Modelsim 10.1d
Mentor Graphics Corporation, 2012, Version 10.1d, 80 pages
The last one, number 9, is from the help-file of the current ModelSim on my laptop.


Which version of ModelSim do I have?
ModelSim-Altera 10.1d (Quartus II 13.1)

ModelSim tutorial

I took the plunge and folowed the tutorial from the help-file in ModelSim, described above as #9.
It took me only one Sunday-afternoon and I had the counter-example running. The remaining evening was used to play with ModelSim. Awesome!
So, read the tutorial, take your time, and just try. This investment is worth your time!

Test-source for my DC-remover

Fort testing my DC-remover I need a simple source that I can simulate with ModelSim.
I want to construct a simple source that delivers a sinus with an offset.
My source has a 12-bit 2s-complement output. The average output will be 600.
12 bit is 4096 max, so +/- 2048. Let’s make my sinus 600 + 1400*sin(wt), so the outout-value will be 600-1400= -800 … 600+1400=2000.
The output will be -800…2000.
If my DC-remover is OK, the final output will be +/- 1400, so the original sine with DC-offset removed.

Port specification:

entity datasource is
port (
     reset : in std_logic;
clk  : in std_logic;
sdata : out signed(11 downto 0)
      ) ;
end entity;

samplerate is 4 MHz. I want 32 samples/period. So my sine has a frequency of 4/32 MHz = 125 kHz.

I need an internal 5 bits counter. This counter is reset by the signal reset and increments with every clk-pulse.
The 5-bits output of this counter selects a number from a table. Perhaps a case statement.

The code-fragment for the counter:

  signal index: std_logic_vector(4 downto 0);
      process (clk, reset)
        begin 
          if (reset='1') then 
            tmp <= "00000"; 
          elsif (rising_edge(clk)) then 
            index <= index + 1;
          end if; 
      end process;

And the case=statement to calculate the 12-bit output:

signal tmpdata: std_logic_vector(11 downto 0);
     myproc: process (index)
     begin
          case(index) is
          when “00000” => tmpdata <= “000000000000”;
          when “00001” => tmpdata <= “000000111111”;
          ….
          when “11110” => tmpdata <= “111111100000”;
          when “11111” => tmpdata <= “111111111000”;
          when others => tmpdata ,= “000000000000”;
          end case;
     end process my_proc;

     sdata <= tmpdata;

The right values for the right-side of the when can be calculated with a simple python-program

(and I must admit I did not study the tobin-code, but it works (me coward!))

# GenVHDLsin01.py
# Apr-2014 Kees de Groot
#
# generate VHDL when statements for sine-wave

import pylab as pl
import numpy as np
from math import sin, cos, pi

n = 32

def tobin(x, count=8):
        """
        Integer to binary
        Count is number of bits
        """
        return "".join(map(lambda y:str((x>>y)&1), range(count-1, -1, -1)))

for i in range(n):
    data = sin(2 * pi * i / n)
    output = 600 + 1400 * data
    intout = int(output)
    #print i, data, output, intout, bin(intout), tobin(intout,12)
    print "when", '"'+ tobin(i,5)+'"'," => tmpdata <= ", '"' + tobin(intout,12) + '"'


which outputs:

when "00000"  => tmpdata <=  "001001011000"
when "00001"  => tmpdata <=  "001101101001"
when "00010"  => tmpdata <=  "010001101111"
when "00011"  => tmpdata <=  "010101100001"
when "00100"  => tmpdata <=  "011000110101"
when "00101"  => tmpdata <=  "011011100100"
when "00110"  => tmpdata <=  "011101100101"
when "00111"  => tmpdata <=  "011110110101"
when "01000"  => tmpdata <=  "011111010000"
when "01001"  => tmpdata <=  "011110110101"
when "01010"  => tmpdata <=  "011101100101"
when "01011"  => tmpdata <=  "011011100100"
when "01100"  => tmpdata <=  "011000110101"
when "01101"  => tmpdata <=  "010101100001"
when "01110"  => tmpdata <=  "010001101111"
when "01111"  => tmpdata <=  "001101101001"
when "10000"  => tmpdata <=  "001001011000"
when "10001"  => tmpdata <=  "000101000110"
when "10010"  => tmpdata <=  "000001000000"
when "10011"  => tmpdata <=  "111101001111"
when "10100"  => tmpdata <=  "111001111011"
when "10101"  => tmpdata <=  "110111001100"
when "10110"  => tmpdata <=  "110101001011"
when "10111"  => tmpdata <=  "110011111011"
when "11000"  => tmpdata <=  "110011100000"
when "11001"  => tmpdata <=  "110011111011"
when "11010"  => tmpdata <=  "110101001011"
when "11011"  => tmpdata <=  "110111001100"
when "11100"  => tmpdata <=  "111001111011"
when "11101"  => tmpdata <=  "111101001111"
when "11110"  => tmpdata <=  "000001000000"
when "11111"  => tmpdata <=  "000101000110"

So, the case=statement to calculate the 12-bit output:

signal tmpdata: std_logic_vector(11 downto 0);
     myproc: process (index)
     begin
          case(index) is
when "00000"  => tmpdata <=  "001001011000"
when "00001"  => tmpdata <=  "001101101001"
when "00010"  => tmpdata <=  "010001101111"
when "00011"  => tmpdata <=  "010101100001"
when "00100"  => tmpdata <=  "011000110101"
when "00101"  => tmpdata <=  "011011100100"
when "00110"  => tmpdata <=  "011101100101"
when "00111"  => tmpdata <=  "011110110101"
when "01000"  => tmpdata <=  "011111010000"
when "01001"  => tmpdata <=  "011110110101"
when "01010"  => tmpdata <=  "011101100101"
when "01011"  => tmpdata <=  "011011100100"
when "01100"  => tmpdata <=  "011000110101"
when "01101"  => tmpdata <=  "010101100001"
when "01110"  => tmpdata <=  "010001101111"
when "01111"  => tmpdata <=  "001101101001"
when "10000"  => tmpdata <=  "001001011000"
when "10001"  => tmpdata <=  "000101000110"
when "10010"  => tmpdata <=  "000001000000"
when "10011"  => tmpdata <=  "111101001111"
when "10100"  => tmpdata <=  "111001111011"
when "10101"  => tmpdata <=  "110111001100"
when "10110"  => tmpdata <=  "110101001011"
when "10111"  => tmpdata <=  "110011111011"
when "11000"  => tmpdata <=  "110011100000"
when "11001"  => tmpdata <=  "110011111011"
when "11010"  => tmpdata <=  "110101001011"
when "11011"  => tmpdata <=  "110111001100"
when "11100"  => tmpdata <=  "111001111011"
when "11101"  => tmpdata <=  "111101001111"
when "11110"  => tmpdata <=  "000001000000"
when "11111"  => tmpdata <=  "000101000110"
          when others => tmpdata <= “000000000000”;
          end case;
     end process my_proc;

     sdata <= tmpdata;

Now collect all fragments and produce the VHDL-program datasource.vhd

Note that I forgot the semicolons, not too much editing adding 32 semicolons...

-- datasource.vhd
-- May-2014 Kees de Groot
-- test-program for my DC-remover
-- this device outputs a 2s-complement sine of 32 points

entity datasource is
port (
       reset  : in   std_logic;
clk    : in   std_logic;
sdata  : out  signed(11 downto 0)
      ) ;
end;

architecture mydatasource of datasource is
begin
  signal index: std_logic_vector(4 downto 0);
 calcindex:     process (clk, reset)
        begin 
          if (reset='1') then 
            tmp <= "00000"; 
          elsif (rising_edge(clk)) then 
            index <= index + 1;
          end if; 
      end process calcindex;
signal tmpdata: std_logic_vector(11 downto 0);
       myproc: process (index)
       begin
             case(index) is
when "00000"  => tmpdata <=  "001001011000";
when "00001"  => tmpdata <=  "001101101001";
when "00010"  => tmpdata <=  "010001101111";
when "00011"  => tmpdata <=  "010101100001";
when "00100"  => tmpdata <=  "011000110101";
when "00101"  => tmpdata <=  "011011100100";
when "00110"  => tmpdata <=  "011101100101";
when "00111"  => tmpdata <=  "011110110101";
when "01000"  => tmpdata <=  "011111010000";
when "01001"  => tmpdata <=  "011110110101";
when "01010"  => tmpdata <=  "011101100101";
when "01011"  => tmpdata <=  "011011100100";
when "01100"  => tmpdata <=  "011000110101";
when "01101"  => tmpdata <=  "010101100001";
when "01110"  => tmpdata <=  "010001101111";
when "01111"  => tmpdata <=  "001101101001";
when "10000"  => tmpdata <=  "001001011000";
when "10001"  => tmpdata <=  "000101000110";
when "10010"  => tmpdata <=  "000001000000";
when "10011"  => tmpdata <=  "111101001111";
when "10100"  => tmpdata <=  "111001111011";
when "10101"  => tmpdata <=  "110111001100";
when "10110"  => tmpdata <=  "110101001011";
when "10111"  => tmpdata <=  "110011111011";
when "11000"  => tmpdata <=  "110011100000";
when "11001"  => tmpdata <=  "110011111011";
when "11010"  => tmpdata <=  "110101001011";
when "11011"  => tmpdata <=  "110111001100";
when "11100"  => tmpdata <=  "111001111011";
when "11101"  => tmpdata <=  "111101001111";
when "11110"  => tmpdata <=  "000001000000";
when "11111"  => tmpdata <=  "000101000110";
             when others => tmpdata <= “000000000000”;
             end case;
       end process my_proc;

       sdata <= tmpdata;

end mydatasource;


I need a testbed, the VHDL-program tdatasource.vhd

-- tdatasource.vhd
-- May-2014 Kees de Groot
-- test-program for the datasource to test my DC-remover
-- this device simply tests the datasource

entity test_datasource is
    PORT ( sourceout : out signed(11 downto 0);
end;

architecture mytest of test_datasourece is

COMPONENT datasource
port (
       reset  : in   std_logic;
clk    : in   std_logic;
sdata  : out  signed(11 downto 0)
      ) ;
END COMPONENT ;

SIGNAL clk   : bit := '0';
SIGNAL reset : bit := '0';

begin

dut : datasource
   PORT MAP (
   sdata => sdata,
   clk => clk,
   reset => reset );

clock : PROCESS
   begin
   wait for 10 ns; clk  <= not clk;
end PROCESS clock;

stimulus : PROCESS
   begin
   wait for 5 ns; reset  <= '1';
   wait for 4 ns; reset  <= '0';
   wait;
end PROCESS stimulus;

end only;

I copy these two files datasource.vhd and tdatasource.vhd to C:\altera\work
Then start ModelSim, point to this directory, compile, start simulation and run

I start NotePad, paste the above source in NotePad, then save it to C:\altera\work\datasource.vhd
The same for tdatasource.vhd

Now I should have both files in C:\altera\work:



The other files from the tutorial are still there but that does not harm hopefully…


Now start ModelSim and point to that directory



Simply close this screen

Oops, it starts the last project,
So File > New Project



click on Add existing File



Browse and add datasource and tdatasource





click Create Simulation



Cancel

Simulate  > start simulation

Cancel

I have to compile first!

Compile > compile all

# Compile of tdatasource.vhd failed with 2 errors.
# 2 compiles, 2 failed with 6 errors.

Of course, 1st time!


I did not specify any library, I thought that was not necessary for a simulation: stupid!

doubleclick on t\datasource.vhd in the project menu at the left opens the source in WordPad.
I have to add libraries: which? IEEE…??
 Parenthesis missing
Correct  to

    PORT ( sourceout : out     signed(11 downto 0));

Save and compile again


Same error

I still think it is a library problem

I’ll have a look in the LMS-bladerf file and copy the IEE lib spec

Add

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

to the start of both vhd-files

Save and compile again



Typo datasourece, should be datasource

Now I see:

# 2 compiles, 2 failed with 42 errors.
# Compile of datasource.vhd failed with 40 errors.
# Compile of tdatasource.vhd failed with 2 errors.
# 2 compiles, 2 failed with 42 errors.




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


-- tdatasource.vhd
-- May-2014 Kees de Groot
-- test-program for the datasource to test my DC-remover
-- this device simply tests the datasource

entity test_datasource is
    PORT ( sdata : out     signed(11 downto 0));
end;

architecture mytest of test_datasource is

COMPONENT datasource
port (
       reset  : in   std_logic;
clk    : in   std_logic;
sdata  : out  signed(11 downto 0)
      ) ;
END COMPONENT ;

SIGNAL clk   : std_logic := '0';
SIGNAL reset : std_logic := '0';

begin

dut : datasource
   PORT MAP (
   sdata => sdata,
   clk => clk,
   reset => reset );

clock : PROCESS
   begin
   wait for 10 ns; clk  <= not clk;
end PROCESS clock;

stimulus : PROCESS
   begin
   wait for 5 ns; reset  <= '1';
   wait for 4 ns; reset  <= '0';
   wait;
end PROCESS stimulus;

end mytest;

tdatasource.vhd is now succesfull compiled!

Now concentrate on datasource.vhd

Doubleclick in the lower window on the red error-message opens a window with the errormessages

Double click on the file datasource.vhd to open it in the editor

After some corrections the source compiles without errors:

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

-- datasource.vhd
-- May-2014 Kees de Groot
-- test-program for my DC-remover
-- this device outputs a 2s-complement sine of 32 points

entity datasource is
port (
       reset  : in   std_logic;
       clk    : in   std_logic;
       sdata  : out  signed(11 downto 0)
      ) ;
end datasource;

architecture mydatasource of datasource is
  signal tmpdata: signed(11 downto 0);
begin
 cal  signal indexx: signed(4 downto 0);
cindex: process (clk, reset)
        begin 
          if (reset='1') then 
            tmpdata <= "000000000000"; 
          elsif (rising_edge(clk)) then 
            indexx <= indexx + 1;
          end if; 
      end process calcindex;
       myproc: process (indexx)
       begin
             case(indexx) is
when "00000"  => tmpdata <=  "001001011000";
when "00001"  => tmpdata <=  "001101101001";
when "00010"  => tmpdata <=  "010001101111";
when "00011"  => tmpdata <=  "010101100001";
when "00100"  => tmpdata <=  "011000110101";
when "00101"  => tmpdata <=  "011011100100";
when "00110"  => tmpdata <=  "011101100101";
when "00111"  => tmpdata <=  "011110110101";
when "01000"  => tmpdata <=  "011111010000";
when "01001"  => tmpdata <=  "011110110101";
when "01010"  => tmpdata <=  "011101100101";
when "01011"  => tmpdata <=  "011011100100";
when "01100"  => tmpdata <=  "011000110101";
when "01101"  => tmpdata <=  "010101100001";
when "01110"  => tmpdata <=  "010001101111";
when "01111"  => tmpdata <=  "001101101001";
when "10000"  => tmpdata <=  "001001011000";
when "10001"  => tmpdata <=  "000101000110";
when "10010"  => tmpdata <=  "000001000000";
when "10011"  => tmpdata <=  "111101001111";
when "10100"  => tmpdata <=  "111001111011";
when "10101"  => tmpdata <=  "110111001100";
when "10110"  => tmpdata <=  "110101001011";
when "10111"  => tmpdata <=  "110011111011";
when "11000"  => tmpdata <=  "110011100000";
when "11001"  => tmpdata <=  "110011111011";
when "11010"  => tmpdata <=  "110101001011";
when "11011"  => tmpdata <=  "110111001100";
when "11100"  => tmpdata <=  "111001111011";
when "11101"  => tmpdata <=  "111101001111";
when "11110"  => tmpdata <=  "000001000000";
when "11111"  => tmpdata <=  "000101000110";
when others => tmpdata <= "000000000000";
             end case;
       end process myproc;

       sdata <= tmpdata;

end mydatasource;

Simulate > start simulation


Quite encouraging:



Right click on the objects windows and “add wave”

Click on run

Lower left type “run 500 ns”



Well, not any output still…

It looks like indexx is never initialized, so remains at “XXXXX”

Change

          if (reset='1') then 
            tmpdata <= "000000000000";
            index <= "00000"; 
          elsif (rising_edge(clk)) then 

end simulation
save, compile, simulate, run




It looks like some signals are not defined yet!

I get XXX in the wave-display.

Why? Exactly what Brian explained: define signals in one process, at one place only. No multiple assignments! If there more sources, one source tells "it is a ONE!" and another source yells "it is a ZERO" then what shouyld be the outcome??

I changed my source and now it is ok. ModelSim is awesome indeed!!!

I tested my datasource.vhd and now I can finally test my dcremover.vhd in the same way.


Here is the final source of datasource.vhd

Note that I renamed index to indexx because index might be a reserved word perhaps?

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

-- datasource.vhd
-- May-2014 Kees de Groot
-- test-program for my DC-remover
-- this device outputs a 2s-complement sine of 32 points

entity datasource is
port (
       reset  : in   std_logic;
       clk    : in   std_logic;
       sdata  : out  signed(11 downto 0)
      ) ;
end datasource;

architecture mydatasource of datasource is
  signal indexx: signed(4 downto 0);
  signal tmpdata: signed(11 downto 0);
begin
 calcindex: process (clk, reset)
        begin 
          if (reset='1') then 
            indexx <= "00000"; 
          elsif (rising_edge(clk)) then 
            indexx <= indexx + 1;
          end if; 
      end process calcindex;
       myproc: process (indexx)
       begin
             case(indexx) is
when "00000"  => tmpdata <=  "001001011000";
when "00001"  => tmpdata <=  "001101101001";
when "00010"  => tmpdata <=  "010001101111";
when "00011"  => tmpdata <=  "010101100001";
when "00100"  => tmpdata <=  "011000110101";
when "00101"  => tmpdata <=  "011011100100";
when "00110"  => tmpdata <=  "011101100101";
when "00111"  => tmpdata <=  "011110110101";
when "01000"  => tmpdata <=  "011111010000";
when "01001"  => tmpdata <=  "011110110101";
when "01010"  => tmpdata <=  "011101100101";
when "01011"  => tmpdata <=  "011011100100";
when "01100"  => tmpdata <=  "011000110101";
when "01101"  => tmpdata <=  "010101100001";
when "01110"  => tmpdata <=  "010001101111";
when "01111"  => tmpdata <=  "001101101001";
when "10000"  => tmpdata <=  "001001011000";
when "10001"  => tmpdata <=  "000101000110";
when "10010"  => tmpdata <=  "000001000000";
when "10011"  => tmpdata <=  "111101001111";
when "10100"  => tmpdata <=  "111001111011";
when "10101"  => tmpdata <=  "110111001100";
when "10110"  => tmpdata <=  "110101001011";
when "10111"  => tmpdata <=  "110011111011";
when "11000"  => tmpdata <=  "110011100000";
when "11001"  => tmpdata <=  "110011111011";
when "11010"  => tmpdata <=  "110101001011";
when "11011"  => tmpdata <=  "110111001100";
when "11100"  => tmpdata <=  "111001111011";
when "11101"  => tmpdata <=  "111101001111";
when "11110"  => tmpdata <=  "000001000000";
when "11111"  => tmpdata <=  "000101000110";
when others => tmpdata <= "000000000000";
             end case;
       end process myproc;

       sdata <= tmpdata;

end mydatasource;

Somehow I found this URL on the web:


Analogue Waveforms

Modelsim can display a signal in your VHDL/Verilog design as an analogue signal. One use for this is when you want to visualise the output of a digital to analogue converter to see if you have synthesised the waveform as you intended. To do this you can right-click on the signal name in the Wave window and select it's properties. Switch to the Format tab and select 'Analogue'. You can optionally set a maximum value for the signal so that Modelsim can stretch your waveform to fit the height of the row. For example, set it to 255 for an 8-bit DAC value.






 It is even possible to get a decimal value of the wave under the cursor!!

Epilogue

Yes, ModelSim is awesome indeed. Testbenches are not difficult to construct once you grasp the overall idea. Now I am gonna make a testbench for my DC-remover using the above datasource.

Don't be afraid/shy study Altera's tutorial!





3 comments:

  1. I am glad you were able to get ModelSim to work for you!

    Some tips - there is an ieee.math_real library which will give you floating point numbers as well as sin(), cos(), etc. VHDL is very strict with this type, so you need a decimal point to define it as a real number as such:

    constant val : real := 0.0 ;

    There is also an ieee.math_complex which gives you a record for the real/imaginary pair.

    Next, you are probably wondering how to get back and forth between types. When it comes to conversion between integer and real, you just need a normal cast.

    constant VAL : integer := integer(4096.0*cos(MATH_PI/7.0)) ;

    The converse is the same for integer to real:

    constant VAL : real := real(4096*2) ;

    To get between integer and signed/unsigned types, there are the to_ functions, as such:

    constant SVAL : signed(15 downto 0) := to_signed(4096,16) ;
    constant IVAL : integer := to_integer(SVAL) ;

    The second argument in to_signed() is the length of the resulting vector. To make things generic and easy, it's best to use the 'length of a signal you've already defined:

    signal someval : signed(15 downto 0) ;
    ...
    someval <= to_signed(14, someval'length) ;

    So you know you are always going to convert to that length and not get length mismatch errors.

    Lastly, I noticed your clock statement was a little more cumbersome than it had to be. What I typically do is:

    signal clock : std_logic := '1' ;
    ...
    clock <= not clock after 1 ns ;

    If you wish to setup an actual frequency, you can do something like this:

    constant FS : real := 38.4e6 ;
    ...
    clock <= not clock after (1.0/FS)/2.0 * (1 sec) ;

    The reason for the other division by 2.0 is to make it a half pulse width transition.

    I hope this is useful for you. I know I find ModelSim to be extremely useful in vetting out designs in general!

    ReplyDelete
    Replies
    1. I would be careful with using the ieee.math_real and ieee.math_complex libraries. Are they fully synthesizable?

      Delete
    2. You can use them, in synthesizable code, if they are only referenced in the declarative region of the architecture and not the statement part (aka: before the 'begin' statement).

      There are synthesizable fixed and floating point packages written by David Bishop for the VHDL-2008 standard.

      Personally I would stay away from the floating point due to the size, but the fixed point packages can be useful if you don't like doing the fixed point math yourself.

      Delete