Author Topic: VHDL sine wave generator generates only half a sinusoid  (Read 3495 times)

0 Members and 1 Guest are viewing this topic.

Offline DehimTopic starter

  • Newbie
  • Posts: 4
  • Country: nl
VHDL sine wave generator generates only half a sinusoid
« on: December 09, 2017, 01:13:29 pm »
Hi everyone,

I'm new to this forum and relatively new to the world of FPGAs. I have a Zybo development board and I'm trying to generate a sine wave at the output of the green pin of the vga port. I've made a lookup table for the first quadrant of a sinewave and the idea was to flip it vertically and horizontally to get the other quadrants. However, the part that's supposed to flip it horizontally, flips it every other quadrant, instead of every other half period like it should.

Here's the code of the package that's responsible for generating the sine wave:

Code: [Select]
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

package trig is
    function sin
    (
        t : integer --theta
    )
    return integer;
end package trig;

package body trig is
    function sin
    (
        t : integer
    )
    return integer is variable result : integer range -32 to 31;
    variable resPH : integer range 0 to 31; --result PlaceHolder
    constant tRes : integer := 2**31-1; --theta Resolution
    variable tQ : integer range 0 to tRes / 4; --theta Quadrant
    variable tH : integer range 0 to tRes / 2;  --theta Half
    variable tP : integer range 0 to tRes; --theta Period
    --
    begin
       
        --part responsible for flipping vertically
        tH := t rem tRes/2;
       
        if tH < tRes / 4 then
            tQ := tH;
        else
            tQ := tRes / 2 - tH;
        end if;


        -- lookuptable
        if tQ < 10682447 then
            resPH := 0;
        elsif tQ < 21375347 then
            resPH := 1;
        elsif tQ < 32089246 then
            resPH := 2;
        elsif tQ < 42834877 then
            resPH := 3;
        elsif tQ < 53623259 then
            resPH := 4;
        elsif tQ < 64465806 then
            resPH := 5;
        elsif tQ < 75374440 then
            resPH := 6;
        elsif tQ < 86361724 then
            resPH := 7;
        elsif tQ < 97441013 then
            resPH := 8;
        elsif tQ < 108626624 then
            resPH := 9;
        elsif tQ < 119934038 then
            resPH := 10;
        elsif tQ < 131380144 then
            resPH := 11;
        elsif tQ < 142983531 then
            resPH := 12;     
        elsif tQ < 154764850 then
            resPH := 13;
        elsif tQ < 166747255 then
            resPH := 14;
        elsif tQ < 178956971 then
            resPH := 15;
        elsif tQ < 191424016 then
            resPH := 16;
        elsif tQ < 204183141 then
            resPH := 17;
        elsif tQ < 217275080 then
            resPH := 18;
        elsif tQ < 230748236 then
            resPH := 19;
        elsif tQ < 244661020 then
            resPH := 20;
        elsif tQ < 259085173 then
            resPH := 21;
        elsif tQ < 274110625 then
            resPH := 22;
        elsif tQ < 289852894 then
            resPH := 23;
        elsif tQ < 306464809 then
            resPH := 24;   
        elsif tQ < 324156168 then
            resPH := 25;
        elsif tQ < 343229061 then
            resPH := 26;
        elsif tQ < 364147454 then
            resPH := 27;
        elsif tQ < 387693376 then
            resPH := 28;
        elsif tQ < 415394116 then
            resPH := 29;
        elsif tQ < 451201159 then
            resPH := 30;
        else
            resPH := 31;
        end if;     
        --               
       
        --part responsible for flipping horizontally
        tP := t rem tRes;
       
        if tP < tRes / 2 then
            result := resPH;
        else
            result := -resPH - 1;
        end if;
       
    return result;
    end sin;
end package body trig;

and here's the code in my top module:

Code: [Select]
----------------------------------------------------------------------------------
-- Company:
-- Engineer:
--
-- Create Date: 05.12.2017 11:36:46
-- Design Name:
-- Module Name: TopModule - Behavioral
-- Project Name:
-- Target Devices:
-- Tool Versions:
-- Description:
--
-- Dependencies:
--
-- Revision:
-- Revision 0.01 - File Created
-- Additional Comments:
--
----------------------------------------------------------------------------------


library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use work.trig.all;

-- Uncomment the following library declaration if using
-- arithmetic functions with Signed or Unsigned values
use IEEE.NUMERIC_STD.ALL;

-- Uncomment the following library declaration if instantiating
-- any Xilinx leaf cells in this code.
--library UNISIM;
--use UNISIM.VComponents.all;

entity TopModule is
    Port
    (
        clk : in STD_LOGIC;
        vga_g : out STD_LOGIC_VECTOR (5 downto 0)
    );
end TopModule;

architecture Behavioral of TopModule is
    signal timer : integer range 0 to 2**31-1 := 0;
   
begin


    process(clk)
    constant frequency : integer := 100000;
    begin
        if rising_edge(clk) then
            if timer < 2**31-1 - frequency then
                timer <= timer + frequency;
            else
                timer <= timer - (2**31-1) + frequency;
            end if;
            vga_g <= std_logic_vector(to_unsigned(sin(timer) + 32, 6));
        end if;
    end process;



end Behavioral;


This is the output of the green vga pin:



What am I doing wrong?

Thanks in advance,
Dehim
« Last Edit: December 09, 2017, 01:21:45 pm by Dehim »
 

Offline Someone

  • Super Contributor
  • ***
  • Posts: 4527
  • Country: au
    • send complaints here
Re: VHDL sine wave generator generates only half a sinusoid
« Reply #1 on: December 09, 2017, 09:46:58 pm »
However, the part that's supposed to flip it horizontally, flips it every other quadrant, instead of every other half period like it should.
Compare the two flipping conditionals

Code: [Select]
        --part responsible for flipping vertically
        tH := t rem tRes/2;
       
        if tH < tRes / 4 then
            tQ := tH;
        else
            tQ := tRes / 2 - tH;
        end if;

Code: [Select]
        --part responsible for flipping horizontally
        tP := t rem tRes;
       
        if tP < tRes / 2 then
            result := resPH;
        else
            result := -resPH - 1;
        end if;
Suddenly jumping from integer math to something looking like floating point, the simulator should be showing you the problem clearly. The use of functions for synthesizable code is a very strong design choice that few teams would use, and then to use integers in the synthesized paths again is rarely done even if they have been constrained.
 

Offline DehimTopic starter

  • Newbie
  • Posts: 4
  • Country: nl
Re: VHDL sine wave generator generates only half a sinusoid
« Reply #2 on: December 09, 2017, 10:38:38 pm »
Thanks for your response Someone,

Are you saying It's my use of the division operator that's the problem?
And I also shouldn't be using a function or integers?
I'll see if I can fix the problem tomorrow.

-Dehim
 

Offline Someone

  • Super Contributor
  • ***
  • Posts: 4527
  • Country: au
    • send complaints here
Re: VHDL sine wave generator generates only half a sinusoid
« Reply #3 on: December 10, 2017, 04:00:55 am »
Are you saying It's my use of the division operator that's the problem?
Integers are not usually used for signals, most synthesizable VHDL code uses std_logic_vector (std_logic_unsigned/std_logic_signed) or unsigned/signed (numeric_std) which makes the binary representations clearer. For simulations or static code this is not a problem and integers can be used more freely. Using constrained integers in synthesizable code is not bad, but the tools have been designed around the vector types and the majority of code is written that way.

And I also shouldn't be using a function or integers?
By making a large function you are unable to add registers to the design and pipeline the code, this is the fundamental work in synthesizable code. All the variables used can be hard to see inside simulation which makes your debugging harder than code using only signals. There are projects that use many very small functions and attach them together with registers in RTL but that is a very advanced coding style for specific requirements.

A similar design is discussed here:
http://zipcpu.com/dsp/2017/08/26/quarterwave.html
 

Offline hamster_nz

  • Super Contributor
  • ***
  • Posts: 2803
  • Country: nz
Re: VHDL sine wave generator generates only half a sinusoid
« Reply #4 on: December 10, 2017, 09:39:28 am »
Here's something similar (only 16 entries in the table, and offset 8-bit output). Not saying it is particularly good but you might find something of value in it.

A couple thing of note:

- It uses unconstrained std_logic_vector for the 'phase' input - it will assume whichever width you use with it.

- The lookup values are the center of the bin (eg at 1/32*pi, 3/32*pi, 5/32*pi... rather than 0/16*pi, 1/16*pi, 2/16*pi...).

- You could calculate the values into the lookup table using math, but then rounding starts being tricky - you are most likely better off tweaking the values outside of the implementation.

- You don't get the full range - minimum value is 1, max is 255. This is because the table value is being added / subtracted from 128, when 127.5 is the center of the range.

FWIW, replacing the clean, simple 64x8-bit ROM with a 16x7-bit ROM and quite a bit of (maybe buggy) LUT logic is most likely not worthwhile. I would even consider replacing it with a block RAM if you have one spare and it adds to performance because of the greater phase resolution.

Code: [Select]
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity sin_lookup is
    Port ( clk      : in  STD_LOGIC;
           in_phase : in  STD_LOGIC_VECTOR;
           out_sine : out STD_LOGIC_VECTOR (7 downto 0));
end sin_lookup;

architecture Behavioral of sin_lookup is
    type t_lookup_table is array(0 to 15) of unsigned(7 downto 0);
    constant lookup_table : t_lookup_table := (
        to_unsigned(   7, 8),
        to_unsigned(  19, 8),
        to_unsigned(  31, 8),
        to_unsigned(  43, 8),
        to_unsigned(  55, 8),
        to_unsigned(  66, 8),
        to_unsigned(  76, 8),
        to_unsigned(  86, 8),
        to_unsigned(  94, 8),
        to_unsigned( 102, 8),
        to_unsigned( 109, 8),
        to_unsigned( 115, 8),
        to_unsigned( 120, 8),
        to_unsigned( 124, 8),
        to_unsigned( 126, 8),
        to_unsigned( 127, 8));
begin

process(clk)
        variable table_index : unsigned(3 downto 0) := (others => '0');
    begin
        if rising_edge(clk) then
            if in_phase(in_phase'high-1) = '0' then
                table_index := (others => '0');
                table_index := table_index + unsigned(in_phase(in_phase'high-2 downto in_phase'high-5));
            else
                table_index := (others => '1');
                table_index := table_index - unsigned(in_phase(in_phase'high-2 downto in_phase'high-5));
            end if;
           
            if in_phase(in_phase'high) = '0' then
                out_sine <=  std_logic_vector(to_unsigned(128,8) + lookup_table(to_integer(table_index)));               
            else
                out_sine <=  std_logic_vector(to_unsigned(128,8) - lookup_table(to_integer(table_index)));               
            end if;
        end if;
    end process;
end Behavioral;
Gaze not into the abyss, lest you become recognized as an abyss domain expert, and they expect you keep gazing into the damn thing.
 

Offline DehimTopic starter

  • Newbie
  • Posts: 4
  • Country: nl
Re: VHDL sine wave generator generates only half a sinusoid
« Reply #5 on: December 10, 2017, 11:18:36 am »
Thanks guys,

I'll be looking into the link you gave me, Someone. It seems to have very useful information.

The code you posted, Hamster_nz is understandable, but I do need really high angular resolution, as this sine wave generator is only the first part in trying to make a fully digital FM transmitter and also a digital function generator.

The reason I used if statements in my code, was to achieve this high angular resolution, without eating up huge amounts of RAM/ROM. I don't know if there's a better way to achieve this.

-Dehim
 

Offline dmills

  • Super Contributor
  • ***
  • Posts: 2093
  • Country: gb
Re: VHDL sine wave generator generates only half a sinusoid
« Reply #6 on: December 10, 2017, 03:14:19 pm »
You really want to end up with the values in block ram, magnitude comparison is a whole mess of logic and you generally have more block ram them equivalent fabric.

Usually a lookup with taylor series interpolation seems to be the way to do high resolution, assuming your part has some sort of DSP block available.

Cordic is the way to go if you only have fabric available, possibly with a small ROM providing the first few bits, you get roughly 1 bit per stage.

Regards, Dan.
 

Offline mrflibble

  • Super Contributor
  • ***
  • Posts: 2051
  • Country: nl
Re: VHDL sine wave generator generates only half a sinusoid
« Reply #7 on: December 10, 2017, 04:06:26 pm »
The code you posted, Hamster_nz is understandable, but I do need really high angular resolution, as this sine wave generator is only the first part in trying to make a fully digital FM transmitter and also a digital function generator.

You could always use the Xilinx DDS Compiler to get you up and running with a working sine generator. By using a known to work bit of IP you free up time that you can use for the rest of your design. DDS Compiler license should also be included in the Webpack edition.
 

Offline DehimTopic starter

  • Newbie
  • Posts: 4
  • Country: nl
Re: VHDL sine wave generator generates only half a sinusoid
« Reply #8 on: December 10, 2017, 07:43:34 pm »
Thx for the tips.

I did at least get my old code to generate an entire sine wave by getting rid of the division operator.
I'll be looking into using Taylor series interpolation.
Using the DDS compiler is a great idea, but I really want to try to write as much of the code as possible myself. Thanks for the tip though.

-Dehim
 

Offline hamster_nz

  • Super Contributor
  • ***
  • Posts: 2803
  • Country: nz
Re: VHDL sine wave generator generates only half a sinusoid
« Reply #9 on: December 10, 2017, 11:09:26 pm »
Did some quick spread-sheeting.

With 6 output bits:

Idea for a 6-bit output is about 0.8% error:

128 entries per quadrant = 0.91% error   +13% more noise
64 entries per quadrant = 1.01% error     +26% more noise
32 entries per quadrant = 1.38% error     +72% more noise
16 entries per quadrant = 2.17% error     +109%  more noise
8 entries per quadrant = 4.1% error         +512%  more noise


« Last Edit: December 11, 2017, 12:31:20 am by hamster_nz »
Gaze not into the abyss, lest you become recognized as an abyss domain expert, and they expect you keep gazing into the damn thing.
 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 14464
  • Country: fr
Re: VHDL sine wave generator generates only half a sinusoid
« Reply #10 on: December 15, 2017, 12:47:52 am »
Hi Dehim,

there are numerous better ways of implementing digital signal generators. That has already been said above so I'm not going to elaborate on that any further, but you may indeed find it interesting to learn about DDS. As a VHDL/FPGA learning exercise though, what you did is ok. Unfortunately, it doesn't work properly, but we'll see in a minute why.

First off, let me tell you what I think are good points in your VHDL code, and what I think is not that good.

The good:
  • The use of IEEE's "numeric_std" library and its corresponding types and conversion functions is good practice. It's the only standard way of doing this. Please don't use the old Synopsys libraries for performing arithmetic on logic vectors, they're non-standard, have their quirks and are pretty much deprecated.
  • The use of packages is also good practise if you want to write reusable code. In addition to functions and procedures, you can add component declarations in package files, so that you can reuse entities without having to declare the components in each source file that are going to use these entities.
  • The use of  the rising_edge and falling_edge functions is also good practice and more readable than their clunky counterparts clk'event and clk=xx. They are also more correct for simulation. I'll let you find out why.
The bad:
  • There is no 'reset' signal in your clocked process. That's bad practise in my opinion. Initial values for signals (as with your 'timer' signal) will work with most FPGA's due to them usually having an internal global reset, but they won't work if synthesized on real silicon. So you should definitely add a reset signal.
  • Your sin function is unmaintainable. Imagine you want to modify the resolution, you pretty much have to rewrite all of your comparisons. The use of all those if's could be changed to using an array, which would be shorter and easier to maintain. You could definitely make it more generic.
  • You could also make your generator entity more generic, which would allow you to change the resolution in bits with just one parameter, for instance. If you don't know how to use generic parameters, you should definitely learn about them.
  • I'm going to talk about that a little below, but I'm not sure you wrote a test bench and simulated your code before testing it on your FPGA. If you didn't, well... you should!

Speaking of simulation and test benches: apart from very trivial VHDL code, I suggest that you always write test benches and simulate your code. Behavioral simulation won't show you everything that can go bad in a VHDL design, but it will at least help you get the behavior right. (Things that can go bad and will usually not get caught by behavioral simulation include race conditions, issues when crossing clock domains... that's something to learn about as well!)

There are lots of commercial and free tools for VHDL simulation. I've used the open-source GHDL which I have found to be very good, especially the latest versions, even on complex designs. I use it along with GTKWave, both on Linux and on Windows. Commercial tools usually simulate much faster, but, except for the free versions (sometimes given away with some FPGA software packages), they can be pretty expensive.

And now, we're getting to the cause of the phase issue you have with the piece of code you posted. At first glance, there didn't seem to be anything inherently wrong with your code, so that was a bit tricky to pinpoint. Once I set up a simulation test bench, the result of the simulation was exactly what you saw on the oscilloscope.

There is nothing wrong with using integers if you take care of constraining their ranges properly, which you did.

The problem lies in the very first expression in your 'sin' function's body:

Code: [Select]
tH := t rem tRes/2;
The intended expression looks fine, but this code doesn't actually compute what you intended, and this is because of operators precedence ! In VHDL, '*', '/', 'rem' and 'mod' have the same precedence. If operators with the same precedence are encountered, they are evaluated left to right. (See: http://vlsi-design-engineers.blogspot.fr/2015/07/vhdl-operators.html)
So what your code does is actually equivalent to:

Code: [Select]
tH := (t rem tRes)/2;
So the fix is to rewrite this line like so:

Code: [Select]
tH := t rem (tRes/2);
And here is the test bench code I used (to get you started):

Code: [Select]
--******************************************************************************
-- SineWaveGenerator
--
--         TestBench.vhd
--         Test Bench for SineWaveGenerator implementation.
--         https://www.eevblog.com/forum/microcontrollers/vhdl-sine-wave-generator-generates-only-half-a-sinusoid/
--
-- Author: SiliconWizard
-- Copyright (c) 2017
--
--******************************************************************************

library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;

use WORK.trig.all;

--******************************************************************************

entity TestBench is
generic
(
MainClockPeriod : time := 100 ns -- Main Clock period
);
end entity;

--******************************************************************************

architecture Behavioral of TestBench is

--******************************************************************************

component TopModule is
Port
(
clk : in STD_LOGIC;
vga_g : out STD_LOGIC_VECTOR (5 downto 0)
);
end component;

--******************************************************************************

signal MainClock, Reset : std_logic;
signal Output : std_logic_vector(5 downto 0);

begin

SineWaveGenerator_Inst1: TopModule
port map
(
clk => MainClock,
vga_g => Output
);

-- Clocks.
Clock1: process
begin
MainClock <= '1';
wait for (MainClockPeriod / 2);
MainClock <= '0';
wait for (MainClockPeriod / 2);
end process;

-- Reset.
Reset1: process
begin
Reset <= '1';
wait for 10 us;
Reset <= '0';
wait;
end process;

end Behavioral;

--******************************************************************************

Attached are before fix/after fix simulation screenshots.
You can try that on your board and let me know how that goes.

For those interested, I maintain GTKWave and GHDL binaries (latest versions) for Windows.
« Last Edit: December 15, 2017, 01:10:24 am by SiliconWizard »
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf