FPGA Function Generator - Page 1
Forum section: electronics / projects

author: comeau
author_rank: Contributor
timestamp: March 11, 2017, 11:02:22 pm
content: Hello everyone,I'm a hobbyist and I recently bought an FPGA to do some tinkering. At some point I decided I would try to make something my lab is missing, a function generator. While I'm sure there is a simple way to do this better, I decided to see if I could make it with the FPGA. Project success and nearing completion. I was able to "code" four different functions: sine, triangle, square and sawtooth. It pulses the FPGA's IO ports to create a 0-3V3 signal which is fed into a high bandwidth opamp. I'm sure there are about a billion electrical and programing rules I am breaking. That's why I'm posting it. It does, in fact, work. I would love comments on how things could be done better.Specs:FPGA: Mojo V3 development board by Embedded Micro using a Xilinx Spartan 6 XC6SLX9 TQG144. There is also an onboard AT MEGA32U4 for programming the FPGA, serial communication and ADC.Opamp: LT1363CN8 by Linear Technology. Has max 36V supply and a quoted 70MHz bandwidth. It is functioning as a non-inverting amplifier. (I think that's the term)Supporting circuitry: There are 14 10k resistors from the output pins of the FPGA to a common bus and input to the non-inverting input of the opamp. There are 2 10k potentiometers. One is a voltage divider to produce a DC offset from 0-5V. The other is across the feedback to adjust the gain of the op amp.Power Supply: The power supply is a 460W computer supply from my old desktop. It is supplying +/- 12V to the opamp and 0 and 5V to the Mojo V3 which has an onboard voltage regulation (3V3). The input rail is clamped by two diodes to protect the FPGA.Code: I coded this in Verilog. It has 4 modes programmed that are able to be set by two mode pins. The sine wave is generated by comparing a counter against a table and turning on and off each pin at specific values. (There is a much better way to do this which I have not yet figured out.)Going further: I should be getting parts this week to solder this into a shield for the Mojo. Ideally I want to be able to control this using USB from a GUI. I also may want to divide the FPGA 50MHz clock so that I can get faster waveform.Also, the scope I'm using is a Link Instruments MSO-19 USB mixed signal scope. It cost me like $300, but I really don't know if I would recommend it. It has some issues.Code:module mojo_top(// 50MHz clock inputinput clk,// Input from reset button (active low)input rst_n,// cclk input from AVR, high when AVR is readyinput cclk,// Outputs to the 8 onboard LEDsoutput[7:0]led,// AVR SPI connectionsoutput spi_miso,input spi_ss,input spi_mosi,input spi_sck,// AVR ADC channel selectoutput [3:0] spi_channel,// Serial connectionsinput avr_tx, // AVR Tx => FPGA Rxoutput avr_rx, // AVR Rx => FPGA Txinput avr_rx_busy, // AVR Rx buffer fullinput [1:0] mode,output [63:0]fnc);wire rst = ~rst_n; // make reset active high// these signals should be high-z when not usedassign spi_miso = 1'bz;assign avr_rx = 1'bz;assign spi_channel = 4'bzzzz;assign led = 8'b0;gen_driver #(.freq(3_000), .duty(50)) g_fnc(.clk(clk),.rst(rst),.mode(mode),.out(fnc));endmoduleCode:module gen_driver #(parameter freq=2000, duty=50)(input clk, // clockinput rst, // resetinput [1:0] mode,output [63:0] out);wire [63: 0] saw_out;wire [63: 0] sine_out;wire [63: 0] square_out;wire [63: 0] triangle_out;reg [63 : 0] gen_out;assign out=gen_out;saw #(.freq(freq)) saw_fnc (.clk(clk),.rst(rst),.fnc(saw_out));sine #(.freq(freq)) sine_fnc (.clk(clk),.rst(rst),.fnc(sine_out));square #(.freq(freq),.duty(duty)) square_fnc (.clk(clk),.rst(rst),.fnc(square_out));triangle #(.freq(freq)) triangle_fnc (.clk(clk),.rst(rst),.fnc(triangle_out));/* Combinational Logic */always @* begincase(mode)2'b00:gen_out<=saw_out;2'b01:gen_out<=sine_out;2'b10:gen_out<=square_out;2'b11:gen_out<=triangle_out;endcaseend/* Sequential Logic */always @(posedge clk) beginif (rst) begin// Add flip-flop reset values hereend else begin// Add flip-flop q <= d statements hereendendendmoduleCode:module square #(parameter freq=2000, duty=50) (input clk, // clockinput rst, // resetoutput [63:0] fnc);parameter clk_freq=26'd50_000_000;reg [25 : 0] counter; //therefore countermax is 26'reg [32 : 0] f; //f max is 26' by 7' = 33'reg [25 : 0] div;initial begindiv=clk_freq/freq; //div max is 26bitsf=div*duty/100;endassign fnc[63:0] = {64{(counter>=f)}};/* Combinational Logic */always @* beginend/* Sequential Logic */always @(posedge clk) beginif (rst) begin// Add flip-flop reset values herecounter <= 25'b0;end else if(counter==div) begincounter<=25'd0;end else begin// Add flip-flop q <= d statements herecounter <= counter+1'b1;endendendmoduleCode:module triangle #(parameter freq=2000) (input clk, // clockinput rst, // resetoutput [63:0] fnc);parameter clk_freq=26'd50_000_000;reg [25 : 0] counter; //therefore countermax is 26'reg [25 : 0] div;initial begindiv=clk_freq/freq; //div max is 26bitsendgenvar i;generatefor (i = 0; i<64;i=i+1) begin: fnc_gen_loopassign fnc = ((counter>=div*(i)/128)&(counter<=div-(div*(i+1)/128)));endendgenerate/* Combinational Logic */always @* beginend/* Sequential Logic */always @(posedge clk) beginif (rst) begin// Add flip-flop reset values herecounter <= 25'b0;end else if(counter==div) begincounter<=25'd0;end else begin// Add flip-flop q <= d statements herecounter <= counter+1'b1;endendendmoduleCode:module sine #(parameter freq = 2000) (input clk, // clockinput rst, // resetoutput [63:0] fnc);reg [25 : 0] div; //div max is 26'reg [25 : 0] counter; //therefore countermax is 26'reg [34 : 0] f[63:0][2:0]; //f max 26' by 9' = 35'initial begindiv=50000000/freq; //div max is 26bitsf=div*113/511;f=div*142/511;f=div+1;f=div*103/511;f=div*153/511;f=div+1;f=div*95/511;f=div*160/511;f=div+1;f=div*89/511;f=div*166/511;f=div+1;f=div*84/511;f=div*171/511;f=div+1;f=div*79/511;f=div*176/511;f=div+1;f=div*75/511;f=div*181/511;f=div+1;f=div*71/511;f=div*185/511;f=div+1;f=div*67/511;f=div*188/511;f=div+1;f=div*63/511;f=div*192/511;f=div+1;f=div*60/511;f=div*196/511;f=div+1;f=div*57/511;f=div*199/511;f=div+1;f=div*53/511;f=div*202/511;f=div+1;f=div*50/511;f=div*205/511;f=div+1;f=div*47/511;f=div*208/511;f=div+1;f=div*44/511;f=div*211/511;f=div+1;f=div*41/511;f=div*214/511;f=div+1;f=div*38/511;f=div*217/511;f=div+1;f=div*35/511;f=div*220/511;f=div+1;f=div*33/511;f=div*223/511;f=div+1;f=div*30/511;f=div*226/511;f=div+1;f=div*27/511;f=div*228/511;f=div+1;f=div*25/511;f=div*231/511;f=div+1;f=div*22/511;f=div*234/511;f=div+1;f=div*19/511;f=div*236/511;f=div+1;f=div*17/511;f=div*239/511;f=div+1;f=div*14/511;f=div*241/511;f=div+1;f=div*11/511;f=div*244/511;f=div+1;f=div*9/511;f=div*247/511;f=div+1;f=div*6/511;f=div*249/511;f=div+1;f=div*4/511;f=div*252/511;f=div+1;f=div*1/511;f=div*254/511;f=div+1;f=0;f=div*257/511;f=div*510/511;f=0;f=div*259/511;f=div*507/511;f=0;f=div*262/511;f=div*505/511;f=0;f=div*264/511;f=div*502/511;f=0;f=div*267/511;f=div*500/511;f=0;f=div*270/511;f=div*497/511;f=0;f=div*272/511;f=div*494/511;f=0;f=div*275/511;f=div*492/511;f=0;f=div*277/511;f=div*489/511;f=0;f=div*280/511;f=div*486/511;f=0;f=div*283/511;f=div*484/511;f=0;f=div*285/511;f=div*481/511;f=0;f=div*288/511;f=div*478/511;f=0;f=div*291/511;f=div*476/511;f=0;f=div*294/511;f=div*473/511;f=0;f=div*297/511;f=div*470/511;f=0;f=div*300/511;f=div*467/511;f=0;f=div*303/511;f=div*464/511;f=0;f=div*306/511;f=div*461/511;f=0;f=div*309/511;f=div*458/511;f=0;f=div*312/511;f=div*454/511;f=0;f=div*315/511;f=div*451/511;f=0;f=div*319/511;f=div*448/511;f=0;f=div*323/511;f=div*444/511;f=0;f=div*326/511;f=div*440/511;f=0;f=div*330/511;f=div*436/511;f=0;f=div*335/511;f=div*432/511;f=0;f=div*340/511;f=div*427/511;f=0;f=div*345/511;f=div*422/511;f=0;f=div*351/511;f=div*416/511;f=0;f=div*358/511;f=div*408/511;f=0;f=div*369/511;f=div*398/511;endgenvar i;generatefor (i = 0; i<(64);i=i+1) begin: fnc_gen_loopassign fnc = ( (counter>=f) & (counter<=f) ) | (counter>=f);//assign fnc[i+1] = fnc;endendgenerate/* Combinational Logic */always @* beginend/* Sequential Logic */always @(posedge clk) beginif (rst) begin// Add flip-flop reset values herecounter <= 31'b0;end else if(counter==div) begincounter<=31'd0;end else begin// Add flip-flop q <= d statements herecounter <= counter+1'b1;endendendmoduleCode:module saw #(parameter freq=2000) (input clk, // clockinput rst, // resetoutput [63:0] fnc);parameter clk_freq=26'd50_000_000;reg [25 : 0] div;reg [25 : 0] counter; //therefore countermax is 26'//reg [32 : 0] f[7:0]; //f max is 26' by 7' = 33'initial begindiv=clk_freq/freq; //div max is 26bitsendgenvar i;generatefor (i = 0; i<64;i=i+1) begin: fnc_gen_loopassign fnc = (counter>=div*(i+1)/64);endendgenerate/* Combinational Logic */always @* beginend/* Sequential Logic */always @(posedge clk) beginif (rst) begin// Add flip-flop reset values herecounter <= 25'b0;end else if(counter==div) begincounter<=25'd0;end else begin// Add flip-flop q <= d statements herecounter <= counter+1'b1;endendendmodule

author: kbarnette
author_rank: Contributor
timestamp: March 12, 2017, 01:42:45 am
content: That's awesome! I'm currently taking a Digital Logic Design class that uses the Nexys 4 DDR board (Artix 7) and I'm definitely giving this a shot.

author: BrianHG
author_rank: Super Contributor
timestamp: March 12, 2017, 02:48:05 am
content: Quote from: comeau on March 11, 2017, 11:02:22 pmTake a look at orolo's integer code here for generating 1/4 a pure sine curve: (I know it's in 'C', but it will translate to verilog fairly easy, read throughout the tread to see improvements & alterations)https://www.eevblog.com/forum/microcontrollers/code-used-for-sine-table/msg1108123/#msg1108123

author: donmr
author_rank: Regular Contributor
timestamp: March 12, 2017, 06:29:17 pm
content: Nice.You do have several registers (counter, div, and f) that are mostly common to all of the functions. You could reduce the gate count by having one set of these that were shared. It might require modifying some of the functions, or maybe not sharing everything.A good synthesis tool can find cases like this and optimize them for you but I think its best not to rely on that.Do you really want all of the functions running at the same time? That uses power and produces electrical noise. And that reduces the changes to share logic.

author: BrianHG
author_rank: Super Contributor
timestamp: March 12, 2017, 06:58:11 pm
content: Quote from: donmr on March 12, 2017, 06:29:17 pm(counter, MULT, and f). The sine generator only uses the most significant bits from the multiply as the new least significant. In verilog/FPGA, the compiler only wires these signals, 0 additional gates or clock cycle steps. Same with the bit shifts to the left and right. The goal of the original code was minimal CPU code and clock cycles. This translates even better into gates in from verilog code.

author: Kleinstein
author_rank: Super Contributor
timestamp: March 12, 2017, 10:09:30 pm
content: Usually the best way to generate a sine wave is the DDS method - thus using a pre-calculated sine table.It still needs an reconstruction filter after the DAC.For a good square wave the way to go would be using the sine and an analog comparator working on the sine signal. For low frequencies a divider after that can improve things. Besides the fixed waveforms an arbitrary waveform might be possible too with an FPGA.

author: kony
author_rank: Regular Contributor
timestamp: March 12, 2017, 10:23:11 pm
content: Everything revolves here (pun intended) around NCO and its phase accumulator.If you desire pure sine of arb frequency, have one quadrant LUT of sine function and use simple decoder logic for sign, or even better use CORDIC. Arbitary square, triangle and sawtooth signals are just about how you handle the output from the phase accumulator.Also use proper DAC with lowpass reconstruction filter, not R2R network.

author: Berni
author_rank: Super Contributor
timestamp: March 13, 2017, 06:40:46 am
content: A R2R DAC will mostly get you up to 8 bit resolution reliably if you use resonably acurate resistors (Don't use 5% tolerance here). If you really want to generate clean and fast signals it might be worth looking in to a high speed DAC chip, tho it will most likely be in a small SMD package so a PCB will likely be needed. At that point some consideration should be given to the noise on the PSU, having local linear regulators for the analog bits can be a good idea(Otherwise a 16bit DAC might quickly turn in to 10bits).As for getting a faster clock you can just use the FPGAs built in PLL to get almost any clock speed you want.