VHDL sine wave generator generates only half a sinusoid - Page 1
Forum section: electronics / general

author: Dehim
author_rank: Newbie
timestamp: December 09, 2017, 01:13:29 pm
content: 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:library ieee;use ieee.std_logic_1164.all;use ieee.numeric_std.all;package trig isfunction sin(t : integer --theta)return integer;end package trig;package body trig isfunction sin(t : integer)return integer is variable result : integer range -32 to 31;variable resPH : integer range 0 to 31; --result PlaceHolderconstant tRes : integer := 2**31-1; --theta Resolutionvariable tQ : integer range 0 to tRes / 4; --theta Quadrantvariable tH : integer range 0 to tRes / 2; --theta Halfvariable tP : integer range 0 to tRes; --theta Period--begin--part responsible for flipping verticallytH := t rem tRes/2;if tH < tRes / 4 thentQ := tH;elsetQ := tRes / 2 - tH;end if;-- lookuptableif tQ < 10682447 thenresPH := 0;elsif tQ < 21375347 thenresPH := 1;elsif tQ < 32089246 thenresPH := 2;elsif tQ < 42834877 thenresPH := 3;elsif tQ < 53623259 thenresPH := 4;elsif tQ < 64465806 thenresPH := 5;elsif tQ < 75374440 thenresPH := 6;elsif tQ < 86361724 thenresPH := 7;elsif tQ < 97441013 thenresPH := 8;elsif tQ < 108626624 thenresPH := 9;elsif tQ < 119934038 thenresPH := 10;elsif tQ < 131380144 thenresPH := 11;elsif tQ < 142983531 thenresPH := 12;elsif tQ < 154764850 thenresPH := 13;elsif tQ < 166747255 thenresPH := 14;elsif tQ < 178956971 thenresPH := 15;elsif tQ < 191424016 thenresPH := 16;elsif tQ < 204183141 thenresPH := 17;elsif tQ < 217275080 thenresPH := 18;elsif tQ < 230748236 thenresPH := 19;elsif tQ < 244661020 thenresPH := 20;elsif tQ < 259085173 thenresPH := 21;elsif tQ < 274110625 thenresPH := 22;elsif tQ < 289852894 thenresPH := 23;elsif tQ < 306464809 thenresPH := 24;elsif tQ < 324156168 thenresPH := 25;elsif tQ < 343229061 thenresPH := 26;elsif tQ < 364147454 thenresPH := 27;elsif tQ < 387693376 thenresPH := 28;elsif tQ < 415394116 thenresPH := 29;elsif tQ < 451201159 thenresPH := 30;elseresPH := 31;end if;----part responsible for flipping horizontallytP := t rem tRes;if tP < tRes / 2 thenresult := resPH;elseresult := -resPH - 1;end if;return result;end sin;end package body trig;and here's the code in my top module:Code:------------------------------------------------------------------------------------ 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 valuesuse 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 isPort(clk : in STD_LOGIC;vga_g : out STD_LOGIC_VECTOR (5 downto 0));end TopModule;architecture Behavioral of TopModule issignal timer : integer range 0 to 2**31-1 := 0;beginprocess(clk)constant frequency : integer := 100000;beginif rising_edge(clk) thenif timer < 2**31-1 - frequency thentimer <= timer + frequency;elsetimer <= 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

author: Someone
author_rank: Super Contributor
timestamp: December 09, 2017, 09:46:58 pm
content: Quote from: Dehim on December 09, 2017, 01:13:29 pmCompare the two flipping conditionalsCode:--part responsible for flipping verticallytH := t rem tRes/2;if tH < tRes / 4 thentQ := tH;elsetQ := tRes / 2 - tH;end if;Code:--part responsible for flipping horizontallytP := t rem tRes;if tP < tRes / 2 thenresult := resPH;elseresult := -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.

author: Dehim
author_rank: Newbie
timestamp: December 09, 2017, 10:38:38 pm
content: 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

author: Someone
author_rank: Super Contributor
timestamp: December 10, 2017, 04:00:55 am
content: Quote from: Dehim on December 09, 2017, 10:38:38 pmIntegers 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.Quote from: Dehim on December 09, 2017, 10:38:38 pmBy 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

author: hamster_nz
author_rank: Super Contributor
timestamp: December 10, 2017, 09:39:28 am
content: 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:library IEEE;use IEEE.STD_LOGIC_1164.ALL;use IEEE.NUMERIC_STD.ALL;entity sin_lookup isPort ( 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 istype 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));beginprocess(clk)variable table_index : unsigned(3 downto 0) := (others => '0');beginif rising_edge(clk) thenif in_phase(in_phase'high-1) = '0' thentable_index := (others => '0');table_index := table_index + unsigned(in_phase(in_phase'high-2 downto in_phase'high-5));elsetable_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' thenout_sine <= std_logic_vector(to_unsigned(128,8) + lookup_table(to_integer(table_index)));elseout_sine <= std_logic_vector(to_unsigned(128,8) - lookup_table(to_integer(table_index)));end if;end if;end process;end Behavioral;

author: Dehim
author_rank: Newbie
timestamp: December 10, 2017, 11:18:36 am
content: 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 needreally highangular 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

author: dmills
author_rank: Super Contributor
timestamp: December 10, 2017, 03:14:19 pm
content: 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.

author: mrflibble
author_rank: Super Contributor
timestamp: December 10, 2017, 04:06:26 pm
content: Quote from: Dehim on December 10, 2017, 11:18:36 amYou could always use theXilinx DDS Compilerto 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.

author: Dehim
author_rank: Newbie
timestamp: December 10, 2017, 07:43:34 pm
content: 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

author: hamster_nz
author_rank: Super Contributor
timestamp: December 10, 2017, 11:09:26 pm
content: 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 noise64 entries per quadrant = 1.01% error +26% more noise32 entries per quadrant = 1.38% error +72% more noise16 entries per quadrant = 2.17% error +109% more noise8 entries per quadrant = 4.1% error +512% more noise

author: SiliconWizard
author_rank: Super Contributor
timestamp: December 15, 2017, 12:47:52 am
content: 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 addcomponentdeclarations 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 therising_edgeandfalling_edgefunctions is also good practice and more readable than their clunky counterpartsclk'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.Yoursinfunction is unmaintainable. Imagine you want to modify the resolution, you pretty much have to rewrite all of your comparisons. The use of all thoseif'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 usegenericparameters, 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 causeof 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:tH := t rem tRes/2;Theintendedexpression looks fine, but this code doesn't actually compute what you intended, and this is because ofoperators 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:tH := (t rem tRes)/2;So the fix is to rewrite this line like so:Code:tH := t rem (tRes/2);And here is the test bench code I used (to get you started):Code:--******************************************************************************-- 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 isgeneric(MainClockPeriod : time := 100 ns-- Main Clock period);end entity;--******************************************************************************architecture Behavioral of TestBench is--******************************************************************************component TopModule isPort(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);beginSineWaveGenerator_Inst1: TopModuleport map(clk => MainClock,vga_g => Output);-- Clocks.Clock1: processbeginMainClock <= '1';wait for (MainClockPeriod / 2);MainClock <= '0';wait for (MainClockPeriod / 2);end process;-- Reset.Reset1: processbeginReset <= '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.