Author Topic: FPGA Function Generator  (Read 5664 times)

0 Members and 1 Guest are viewing this topic.

Offline comeauTopic starter

  • Contributor
  • Posts: 13
  • Country: us
FPGA Function Generator
« on: March 11, 2017, 11:02:22 pm »
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: [Select]
module mojo_top(
    // 50MHz clock input
    input clk,
    // Input from reset button (active low)
    input rst_n,
    // cclk input from AVR, high when AVR is ready
    input cclk,
    // Outputs to the 8 onboard LEDs
    output[7:0]led,
    // AVR SPI connections
    output spi_miso,
    input spi_ss,
    input spi_mosi,
    input spi_sck,
    // AVR ADC channel select
    output [3:0] spi_channel,
    // Serial connections
    input avr_tx, // AVR Tx => FPGA Rx
    output avr_rx, // AVR Rx => FPGA Tx
    input avr_rx_busy, // AVR Rx buffer full
    input [1:0] mode,
    output [63:0]fnc
  );
 
  wire rst = ~rst_n; // make reset active high
 
  // these signals should be high-z when not used
  assign 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)
  );
 
endmodule
Code: [Select]
module gen_driver #(parameter freq=2000, duty=50)(
    input clk,  // clock
    input rst,  // reset
    input [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 @* begin
    case(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;
    endcase
  end
 
  /* Sequential Logic */
  always @(posedge clk) begin
    if (rst) begin
    // Add flip-flop reset values here
    end else begin
    // Add flip-flop q <= d statements here
   
    end
  end
 
endmodule
Code: [Select]
module square #(parameter freq=2000, duty=50) (
    input clk,  // clock
    input rst,  // reset
    output [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 begin
    div=clk_freq/freq; //div max is 26bits
    f=div*duty/100;
  end
 
  assign fnc[63:0] = {64{(counter>=f)}};
 
  /* Combinational Logic */
  always @* begin
 
  end
 
  /* Sequential Logic */
  always @(posedge clk) begin
    if (rst) begin
      // Add flip-flop reset values here
      counter <= 25'b0;
    end else if(counter==div) begin
      counter<=25'd0;
    end else begin
      // Add flip-flop q <= d statements here
      counter <= counter+1'b1;
    end
  end
 
endmodule
Code: [Select]
module triangle #(parameter freq=2000) (
    input clk,  // clock
    input rst,  // reset
    output [63:0] fnc
  );
 
  parameter clk_freq=26'd50_000_000;
  reg [25 : 0] counter; //therefore countermax is 26'
  reg [25 : 0] div;
 
  initial begin
    div=clk_freq/freq; //div max is 26bits
  end
 
  genvar i;
  generate
    for (i = 0; i<64;i=i+1) begin: fnc_gen_loop
    assign fnc[i] = ((counter>=div*(i)/128)&(counter<=div-(div*(i+1)/128)));
    end
  endgenerate
  /* Combinational Logic */
  always @* begin
 
  end
 
  /* Sequential Logic */
  always @(posedge clk) begin
    if (rst) begin
      // Add flip-flop reset values here
      counter <= 25'b0;
    end else if(counter==div) begin
      counter<=25'd0;
    end else begin
      // Add flip-flop q <= d statements here
      counter <= counter+1'b1;
    end
  end
 
endmodule
Code: [Select]
module sine #(
    parameter freq = 2000
  ) (
    input clk,  // clock
    input rst,  // reset
    output [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 begin
    div=50000000/freq; //div max is 26bits
    f[0][0]=div*113/511;f[0][1]=div*142/511;f[0][2]=div+1;
    f[1][0]=div*103/511;f[1][1]=div*153/511;f[1][2]=div+1;
    f[2][0]=div*95/511;f[2][1]=div*160/511;f[2][2]=div+1;
    f[3][0]=div*89/511;f[3][1]=div*166/511;f[3][2]=div+1;
    f[4][0]=div*84/511;f[4][1]=div*171/511;f[4][2]=div+1;
    f[5][0]=div*79/511;f[5][1]=div*176/511;f[5][2]=div+1;
    f[6][0]=div*75/511;f[6][1]=div*181/511;f[6][2]=div+1;
    f[7][0]=div*71/511;f[7][1]=div*185/511;f[7][2]=div+1;
    f[8][0]=div*67/511;f[8][1]=div*188/511;f[8][2]=div+1;
    f[9][0]=div*63/511;f[9][1]=div*192/511;f[9][2]=div+1;
    f[10][0]=div*60/511;f[10][1]=div*196/511;f[10][2]=div+1;
    f[11][0]=div*57/511;f[11][1]=div*199/511;f[11][2]=div+1;
    f[12][0]=div*53/511;f[12][1]=div*202/511;f[12][2]=div+1;
    f[13][0]=div*50/511;f[13][1]=div*205/511;f[13][2]=div+1;
    f[14][0]=div*47/511;f[14][1]=div*208/511;f[14][2]=div+1;
    f[15][0]=div*44/511;f[15][1]=div*211/511;f[15][2]=div+1;
    f[16][0]=div*41/511;f[16][1]=div*214/511;f[16][2]=div+1;
    f[17][0]=div*38/511;f[17][1]=div*217/511;f[17][2]=div+1;
    f[18][0]=div*35/511;f[18][1]=div*220/511;f[18][2]=div+1;
    f[19][0]=div*33/511;f[19][1]=div*223/511;f[19][2]=div+1;
    f[20][0]=div*30/511;f[20][1]=div*226/511;f[20][2]=div+1;
    f[21][0]=div*27/511;f[21][1]=div*228/511;f[21][2]=div+1;
    f[22][0]=div*25/511;f[22][1]=div*231/511;f[22][2]=div+1;
    f[23][0]=div*22/511;f[23][1]=div*234/511;f[23][2]=div+1;
    f[24][0]=div*19/511;f[24][1]=div*236/511;f[24][2]=div+1;
    f[25][0]=div*17/511;f[25][1]=div*239/511;f[25][2]=div+1;
    f[26][0]=div*14/511;f[26][1]=div*241/511;f[26][2]=div+1;
    f[27][0]=div*11/511;f[27][1]=div*244/511;f[27][2]=div+1;
    f[28][0]=div*9/511;f[28][1]=div*247/511;f[28][2]=div+1;
    f[29][0]=div*6/511;f[29][1]=div*249/511;f[29][2]=div+1;
    f[30][0]=div*4/511;f[30][1]=div*252/511;f[30][2]=div+1;
    f[31][0]=div*1/511;f[31][1]=div*254/511;f[31][2]=div+1;
    f[32][0]=0;f[32][1]=div*257/511;f[32][2]=div*510/511;
    f[33][0]=0;f[33][1]=div*259/511;f[33][2]=div*507/511;
    f[34][0]=0;f[34][1]=div*262/511;f[34][2]=div*505/511;
    f[35][0]=0;f[35][1]=div*264/511;f[35][2]=div*502/511;
    f[36][0]=0;f[36][1]=div*267/511;f[36][2]=div*500/511;
    f[37][0]=0;f[37][1]=div*270/511;f[37][2]=div*497/511;
    f[38][0]=0;f[38][1]=div*272/511;f[38][2]=div*494/511;
    f[39][0]=0;f[39][1]=div*275/511;f[39][2]=div*492/511;
    f[40][0]=0;f[40][1]=div*277/511;f[40][2]=div*489/511;
    f[41][0]=0;f[41][1]=div*280/511;f[41][2]=div*486/511;
    f[42][0]=0;f[42][1]=div*283/511;f[42][2]=div*484/511;
    f[43][0]=0;f[43][1]=div*285/511;f[43][2]=div*481/511;
    f[44][0]=0;f[44][1]=div*288/511;f[44][2]=div*478/511;
    f[45][0]=0;f[45][1]=div*291/511;f[45][2]=div*476/511;
    f[46][0]=0;f[46][1]=div*294/511;f[46][2]=div*473/511;
    f[47][0]=0;f[47][1]=div*297/511;f[47][2]=div*470/511;
    f[48][0]=0;f[48][1]=div*300/511;f[48][2]=div*467/511;
    f[49][0]=0;f[49][1]=div*303/511;f[49][2]=div*464/511;
    f[50][0]=0;f[50][1]=div*306/511;f[50][2]=div*461/511;
    f[51][0]=0;f[51][1]=div*309/511;f[51][2]=div*458/511;
    f[52][0]=0;f[52][1]=div*312/511;f[52][2]=div*454/511;
    f[53][0]=0;f[53][1]=div*315/511;f[53][2]=div*451/511;
    f[54][0]=0;f[54][1]=div*319/511;f[54][2]=div*448/511;
    f[55][0]=0;f[55][1]=div*323/511;f[55][2]=div*444/511;
    f[56][0]=0;f[56][1]=div*326/511;f[56][2]=div*440/511;
    f[57][0]=0;f[57][1]=div*330/511;f[57][2]=div*436/511;
    f[58][0]=0;f[58][1]=div*335/511;f[58][2]=div*432/511;
    f[59][0]=0;f[59][1]=div*340/511;f[59][2]=div*427/511;
    f[60][0]=0;f[60][1]=div*345/511;f[60][2]=div*422/511;
    f[61][0]=0;f[61][1]=div*351/511;f[61][2]=div*416/511;
    f[62][0]=0;f[62][1]=div*358/511;f[62][2]=div*408/511;
    f[63][0]=0;f[63][1]=div*369/511;f[63][2]=div*398/511;
  end
  genvar i;
  generate
    for (i = 0; i<(64);i=i+1) begin: fnc_gen_loop
    assign fnc[i] = ( (counter>=f[i][0]) & (counter<=f[i][1]) ) | (counter>=f[i][2]);
    //assign fnc[i+1] = fnc[i];
    end
  endgenerate
  /* Combinational Logic */
  always @* begin
 
  end
 
  /* Sequential Logic */
  always @(posedge clk) begin
    if (rst) begin
      // Add flip-flop reset values here
      counter <= 31'b0;
    end else if(counter==div) begin
      counter<=31'd0;
    end else begin
      // Add flip-flop q <= d statements here
      counter <= counter+1'b1;
    end
  end
 
endmodule
Code: [Select]
module saw #(parameter freq=2000) (
    input clk,  // clock
    input rst,  // reset
    output [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 begin
    div=clk_freq/freq; //div max is 26bits
  end
  genvar i;
  generate
    for (i = 0; i<64;i=i+1) begin: fnc_gen_loop
    assign fnc[i] = (counter>=div*(i+1)/64);
    end
  endgenerate
  /* Combinational Logic */
  always @* begin
 
  end
 
  /* Sequential Logic */
  always @(posedge clk) begin
    if (rst) begin
      // Add flip-flop reset values here
      counter <= 25'b0;
    end else if(counter==div) begin
      counter<=25'd0;
    end else begin
      // Add flip-flop q <= d statements here
      counter <= counter+1'b1;
    end
  end
 
endmodule
« Last Edit: March 11, 2017, 11:15:35 pm by comeau »
 

Offline kbarnette

  • Contributor
  • Posts: 29
  • Country: us
Re: FPGA Function Generator
« Reply #1 on: March 12, 2017, 01:42:45 am »
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.
 

Offline BrianHG

  • Super Contributor
  • ***
  • Posts: 8054
  • Country: ca
Re: FPGA Function Generator
« Reply #2 on: March 12, 2017, 02:48:05 am »
  • 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.)

Take 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
 

Offline donmr

  • Regular Contributor
  • *
  • Posts: 155
  • Country: us
  • W7DMR
Re: FPGA Function Generator
« Reply #3 on: March 12, 2017, 06:29:17 pm »
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.
 

Offline BrianHG

  • Super Contributor
  • ***
  • Posts: 8054
  • Country: ca
Re: FPGA Function Generator
« Reply #4 on: March 12, 2017, 06:58:11 pm »
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.

(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.
« Last Edit: March 12, 2017, 07:00:10 pm by BrianHG »
 

Offline Kleinstein

  • Super Contributor
  • ***
  • Posts: 14724
  • Country: de
Re: FPGA Function Generator
« Reply #5 on: March 12, 2017, 10:09:30 pm »
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.
 

Offline kony

  • Regular Contributor
  • *
  • Posts: 242
  • Country: cz
Re: FPGA Function Generator
« Reply #6 on: March 12, 2017, 10:23:11 pm »
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.
 

Offline Berni

  • Super Contributor
  • ***
  • Posts: 5022
  • Country: si
Re: FPGA Function Generator
« Reply #7 on: March 13, 2017, 06:40:46 am »
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.
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf