element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
FPGA
  • Technologies
  • More
FPGA
Blog FPGA: Waves 3: Computed Sinewave Oscillators
  • Blog
  • Forum
  • Documents
  • Quiz
  • Events
  • Polls
  • Files
  • Members
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
Join FPGA to participate - click to join for free!
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: jc2048
  • Date Created: 30 Sep 2019 4:19 PM Date Created
  • Views 3130 views
  • Likes 12 likes
  • Comments 14 comments
  • xp2
  • microchip
  • brevia2
  • mcp4821
  • experimenting
  • spi
  • fpga
  • vhdl
  • lattice
  • jc2048
  • dac.
  • z-transform
Related
Recommended

FPGA: Waves 3: Computed Sinewave Oscillators

jc2048
jc2048
30 Sep 2019

Previous blogs:


FPGA: Making Waves

FPGA: Waves 2: Simple Sinewave

 

Introduction

 

I have a small Brevia 2 development board [1] from Lattice Semiconductor featuring one of their XP2-family FPGAs. I'm using it to explore, in a very simple, basic kind of way, digital signal generation and processing. This isn't a tutorial, just me experimenting and trying things out.

In the last blog I used one of the block memories in the FPGA as a look-up table in order to generate a sinewave. In the comments to that blog,  michaelkellett  helpfully pointed me to a piece of code that computes a sinewave iteratively, using just a single multiplier. I thought I'd have a go at implementing it in my Lattice FPGA as it would give me some experience of using the multipliers.

 

That experiment wasn't very successful. My attempt to recast it for the 18-bit multipliers in the Lattice device was a mess and didn't work properly. Although at first I got something like a sinewave from it, it was stuck on a single frequency and didn't respond to changing the parameters in the way it should have done.

 

image

 

Fiddling about with it made for some very entertaining waveforms, though. Here's an example:

 

image

 

At that point I realised that I wasn't even implementing the correct form. I had implemented the simple Biquad oscillator thinking that it was the same as the oscillator given in that testbench, but it isn't: the one in the testbench has a couple of extra delays. I tried adding those, but had even less success - it now didn't do anything. I think that was down to my arithmetic and not handling the fixed-point stuff properly but, at this

stage, I was getting a bit fed up with it so I decided to try implementing some other form of oscillator (partly because I was concerned that I might not be using the multiplier and subtracter components correctly).

 

Magic Roundabout Oscillator

 

This oscillator - 'Magic Roundabout' is my own name for it, it often gets called 'Magic Circle' - works on an alternative principle, namely modelling a rotating vector. Although the details are hidden and not obvious from simply looking at the code, essentially it's matrix multiplication where the matrix does rotation of a unit vector about the origin. [I found that out by reading about it - I wouldn't have puzzled it out for myself.] The

number of multiplications needed can be reduced to two multiplies rather than the four you might expect and, though there are two multipliers, they take the same parameter, so simplifying the frequency control of it.

 

Here's a diagram of its form:

 

image

 

If you're not used to such diagrams, the boxes marked z^-1 are storage elements and latch their inputs at each sample time, so providing a single sample-period delay.

 

This oscillator has two state variables. One is the sine output, the other is almost the quadrature of that but, if I'm understanding it correctly, differs by the amount needed to drive the process forward (it's almost, but not quite, the cosine, but the difference from the true cosine will vary according to the frequency selected and the inaccuracy will get quite bad at the top end of the frequency range). A nice feature of it is that the

frequency control parameter can be varied on a sample by sample basis [there's no previous history that would need to be adjusted], however I don't know if it remains constant-amplitude if you do that a lot.

 

Here's the VHDL (this is actually the code for the two-frequency test at the end).

 

 

------------------------------------------------------------------
--    ***** waves_magic *****       --
--  Test of MCP4821 DAC with sine wave from magic circle osc    --
------------------------------------------------------------------
-- JC 28th September 2019          --
------------------------------------------------------------------
-- Rev    Date         Comments         --
-- 01     28-Sept-2019            --
------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;
entity wave_test_con is port(
  clk_in:   in std_logic;        --- system clock in (50 MHz oscillator)
  --- DAC connections
  mcp4821_ncs: out std_logic;        --- DAC cs
  mcp4821_sck: out std_logic;        --- DAC sck
  mcp4821_sdi: out std_logic;        --- DAC sdi
  mcp4821_nldac: out std_logic;        --- DAC load
  --- misc control signals on evaluation board that it might be good to hold at fixed levels
  spi_cs:   out std_logic;        --- 
  hold_n:   out std_logic;        --- 
  sram_cen:  out std_logic;        --- 
  sram_oen:  out std_logic;        --- 
  sram_wen:  out std_logic;        --- 
  uart_tx:  out std_logic);        --- 
 
end wave_test_con;
architecture arch_wave_test of wave_test_con is
signal spi_send, spi_ncs, spi_ncs_del: std_logic;
---signal waveform_count: std_logic_vector (11 downto 0);
signal spi_output_sr_bit_count: std_logic_vector (5 downto 0);
signal spi_output_sr: std_logic_vector (15 downto 0);
signal interval_count: std_logic_vector (8 downto 0);
signal sample_count: std_logic_vector (15 downto 0);
signal sine_value: std_logic_vector (17 downto 0);
signal mult1_product: std_logic_vector (35 downto 0);
signal mult2_product: std_logic_vector (35 downto 0);
signal add_result: std_logic_vector (17 downto 0);
signal sub_result: std_logic_vector (17 downto 0);
signal n1: std_logic_vector (17 downto 0);
signal n2: std_logic_vector (17 downto 0);
signal osc_reset: std_logic_vector (1 downto 0);
signal alpha: std_logic_vector (17 downto 0);
--- declare the multiplier and the subtractor modules as components
component mult_module is
    port (
        DataA: in  std_logic_vector(17 downto 0); 
        DataB: in  std_logic_vector(17 downto 0); 
        Result: out  std_logic_vector(35 downto 0));
end component;
component subtract_module is
    port (
        DataA: in  std_logic_vector(17 downto 0); 
        DataB: in  std_logic_vector(17 downto 0); 
        Result: out  std_logic_vector(17 downto 0));
end component;
component adder_module is
    port (
        DataA: in  std_logic_vector(17 downto 0); 
        DataB: in  std_logic_vector(17 downto 0); 
        Result: out  std_logic_vector(17 downto 0));
end component;
begin
 wave_test_stuff: process (clk_in)
  begin
   if (clk_in'event and clk_in='1') then
   
    --- interval_count counts at the clock rate (50MHz)
    --- counts down by 500, so spi_send occurs every 10us (100kHz)
    if (interval_count(8 downto 0) = b"000000000") then     --- if zero
     interval_count(8 downto 0) <= b"111110011";      --- preset to 499
     spi_send <= '1';
    else
     interval_count(8 downto 0) <= interval_count(8 downto 0) - 1; --- count down
     spi_send <= '0';
    end if;
    --- spi ncs goes low when triggered by spi_send, goes hi again when bitcount reaches 31
    
    if (spi_send = '1') then
     spi_ncs <= '0';
    elsif (spi_output_sr_bit_count(5 downto 0) = b"111111") then
     spi_ncs <= '1';
    end if;
    
    spi_ncs_del <= spi_ncs;
    --- spi output bit count only counts when enable spi cs is low
    
    if (spi_ncs = '0') then
     spi_output_sr_bit_count(5 downto 0) <= spi_output_sr_bit_count(5 downto 0) + 1;
    else
     spi_output_sr_bit_count(5 downto 0) <= b"000000";
    end if;
    --- sample count
    if (spi_send = '1') then
     sample_count(15 downto 0) <= sample_count(15 downto 0) + 1;
     if(osc_reset(1 downto 0) /= b"10") then
      osc_reset <= osc_reset + 1;
     end if;
     if (sample_count(15) = '1') then
      alpha <= b"000000000110011100";  --- 25Hz
     else
      alpha <= b"000001000000010110";  --- 250Hz
     end if;
    end if;
    --- oscillator
    if (spi_send = '1') then
     if (osc_reset(0) = '1') then
      n1(17 downto 0) <= b"000000000000000000";
      n2(17 downto 0) <= b"000111111111110000";
     else
      n1(17 downto 0) <= sine_value(17 downto 0);
      n2(17 downto 0) <= add_result(17 downto 0);
     end if;
    end if;
    --- spi output shift register
    
    if (spi_send = '1') then             --- load...
     spi_output_sr(11 downto 0) <= sine_value(16 downto 5);     ---   
---     spi_output_sr(11 downto 0) <= sine_value(11 downto 0);     ---   
     spi_output_sr(11) <= not sine_value(17);     ---   
     spi_output_sr(15 downto 12) <= b"0011";         ---   dac control bits
    elsif (spi_ncs = '0' and spi_output_sr_bit_count(1 downto 0) = b"11") then --- shift...
     spi_output_sr(15 downto 1) <= spi_output_sr(14 downto 0);    ---  the register contents
     spi_output_sr(0) <= '0';
    end if;
   end if;
   
   ---
   
   mcp4821_ncs <= spi_ncs;
   mcp4821_sck <= spi_output_sr_bit_count(1);
   mcp4821_sdi <= spi_output_sr(15);
   mcp4821_nldac <= '0';
  
   --- hold these device control pins at a fixed level to stop them flapping around
   spi_cs <= '1';
   hold_n <= '1';
   sram_cen <= '1';
   sram_oen <= '1';
   sram_wen <= '1';
   uart_tx <= '1';
  end process wave_test_stuff;
 
  --- instantiate the multipliers, adders and subtractors
  
  mult_module1: mult_module
   port map(
    DataA => alpha,
    DataB => n2(17 downto 0),
    Result => mult1_product(35 downto 0));
    
  mult_module2: mult_module
   port map(
    DataA => alpha,
    DataB => sine_value(17 downto 0),
    Result => mult2_product(35 downto 0));
    
  subtract_module1: subtract_module
   port map(
    DataA => n1(17 downto 0), 
    DataB => mult1_product(35 downto 18), 
    Result => sine_value(17 downto 0));
  adder_module1: adder_module
   port map(
    DataA =>  n2, 
    DataB =>  mult2_product(35 downto 18), 
    Result => add_result(17 downto 0));
end arch_wave_test;

 

In adddition to that code, I used the IPExpress tool in Diamond to create modules for a signed 18-bit multiplier, signed 18-bit adder, and signed 18-bit subtracter which were included in the project and which I then instantiated as components.

 

This time I was immediately more successful. Here's the DAC output for a 1kHz sinewave with a peak-to-peak amplitude of 1V (that's half the DAC range).

 

image

 

Here's a closer view of the sine - keep in mind that there's no reconstruction filter, so we expect to see the steps.

 

image

 

I ran that for about half an hour (about 1.8M cycles) and it didn't show any change of amplitude.

 

Here it is with the code adapted to switch between 250Hz and 25Hz on the fly. I ran that for about 10 minutes without seeing any change of amplitude, but the switch of frequency only occurs about once a second, so you'd need a much longer test to be very sure of it. I had a look at it later and the amplitude had increased slightly, so it looks like it does wander.

 

image

 

Conclusion

 

This has been an interesting diversion and I may come back to it later on, with a further look at some of the variations on these simple ideas that people have invented to get around the problem of dynamically controlling them, but for the moment I'm going to go back to using the table look-up approach as that easily allows me to manipulate frequency and phase without all the complications that come from changing an iterative process on the fly.

 

One thing that is evident from all this is that I need to improve my understanding of discrete-time theory and z transforms.

 

If you found this interesting and would like to see more blogs I've written, a list can be found here: jc2048 Blog Index

 

[1] http://www.latticesemi.com/en/Products/DevelopmentBoardsAndKits/LatticeXP2Brevia2DevelopmentKit.aspx

  • Sign in to reply

Top Comments

  • michaelkellett
    michaelkellett over 5 years ago in reply to neuromodulator +7
    Just to make sure I understand what you are doing: For a fixed frequency: each sample time: angle = angle + phase_increment DAC = sin(angle) and for the sweep: angle = angle + phase_increment DAC = sin…
  • neuromodulator
    neuromodulator over 5 years ago in reply to michaelkellett +5
    I'm playing around with a Spartan S7 ( Learning Verilog with the Digilent Cmod S7 ). So far I have had a great time with it and my plan is to participate in the latest project14 with an FPGA computer graphics…
  • genebren
    genebren over 5 years ago +4
    Great update on FPGA computed sinewave oscillator series. This looks like a very cool way to make sinewaves! Thanks for sharing! Gene
  • jc2048
    jc2048 over 5 years ago

    As an experiment, I thought I'd have a go at a crude frequency sweep of the 'magic roundabout' oscillator.

     

    Here's the result of sweeping by simply incrementing the frequency-setting value (alpha) at each sample time.

     

    image

     

    The 'scope trace is a bit of a mess because of the display aliasing.

     

    Here's near the start in closer detail

     

    image

     

    Here's somewhere around the middle

     

    image

     

    And this is just before the end

     

    image

     

    By the end, the amplitude has increased by about 2%. That's not as much as I was expecting, but it's enough that

    you'd need some kind of adjustment of it if you wanted a continuous oscillator that could vary in

    frequency.

    • Cancel
    • Vote Up +2 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • jeffronium
    jeffronium over 5 years ago in reply to jc2048

    Very smooth. Easy mistake to make. Glad you picked up on it. The working solution was worth pursuing.

    • Cancel
    • Vote Up +3 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • shabaz
    shabaz over 5 years ago

    Hi Jon,

     

    This is really great to see this sine-wave generation capability!! The results look great, and I think it would be fun to try your HDL, I hope to do so on an FPGA board sometime.

    • Cancel
    • Vote Up +3 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • neuromodulator
    neuromodulator over 5 years ago in reply to michaelkellett

    I'm playing around with a Spartan S7 (Learning Verilog with the Digilent Cmod S7). So far I have had a great time with it and my plan is to participate in the latest project14 with an FPGA computer graphics project (Vector Display GPU Project). I'm applying to path to programmable to see if I can get my hands on a Zynq, which I would like to use to the perform high level stuff in the PS and low level in the PL.

    • Cancel
    • Vote Up +5 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • michaelkellett
    michaelkellett over 5 years ago in reply to neuromodulator

    I've only used version 2 seriously - I can't list the VHDL because someone paid me to do it !

    I only used the full resolution for the accumulator but used the hardware 32x32 bit multiply with appropriate shifts and additions.

    In terms of sample rates I had an easy time because the maximum was only 300kHz.

     

    Which FPGA are you using ?

     

    MK

    • Cancel
    • Vote Up +4 Vote Down
    • Sign in to reply
    • More
    • Cancel
>
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2025 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube