1
\$\begingroup\$

I'm trying to implement a low pass filter in Verilog for use on a Red Pitaya's FPGA (Xilinx® Zynq®-7010).

The end goal is to use this as part of a laser locking system using frequency modulation but for now I will tell you what I'm trying to get the filter to do:

  • The only part of the signal I require is the DC component
  • The input signal with have components ranging from 100kHZ to 10MHz in addition to the DC component.

I have encountered a few problems:

  • The Red Pitaya runs off a 125MHz clock and so has a 62.5MHz Nyquist frequency. Filtering with a cutoff around 50kHz means the cutoff ratio is absolutely tiny and requires something like 200 coefficients which is far more than I can fit on the board I think.
  • I could reduce the Nyquist frequency by slowing down the clock (for example firing of every 10th cycle etc...). Using this tool: http://t-filter.engineerjs.com/ a 500kHz sampling frequency requires 53 coefficients (not sure how reliable this tool is).
    • However, this would introduce aliasing from the signals that are higher than 500kHz which I think would be very bad.

So, is there a clever way of simply extracting a DC component from a signal? Otherwise, what can I do to improve on my current design?

My current code is based of this example: http://www.rfwireless-world.com/source-code/VERILOG/Low-pass-FIR-filter-verilog-code.html with a few changes and addition of a test bench. Below is an example for 20 coefficients.

module myModule_tb(); 
    `timescale 1ns/1ns
    wire signed[15:0] d_out; //Final output
    reg reset;               //Reset - active high
    reg signed[15:0] x;      //16-bit conversion of generated wave
    reg valid;               //Valid signal - active high
    reg clk;                 //Standard clock
    wire [31:0] wave;        //32 bit output from DDS

    always begin
 #8 clk =!clk;               //125MHz clock
    reset = 0;
    x<=wave[15:0];           //Take lower 16 bits of wave (only these should be populated)
    valid = 1;               //Begin recording data
    $display (x,"   ",d_out);            //Prints given value to console
    end

    initial begin
 //Initialize clock and reset
 clk = 0;
 reset = 1;

 //End simulation
 #80000
 $finish;
    end

fir_filter fir_filter(d_out, x, clk, reset, valid);

wave_generator wave_generator(
    .aclk(clk),
    .m_axis_data_tdata(wave)
);    

endmodule



module fir_filter(d_out,x,clk,reset,valid); 
//output filtered signal
output signed[15:0] d_out;

//input signals
input signed [15:0] x;
input clk,reset,valid;

//Define 195 16-bit coefficients for FIR filter
wire signed[15:0] b[0:20];

//Coefficient to record number of stored values.
reg[7:0] coeff_add;

//Storage for coefficient multiplication results
reg signed [31:0] temp0,temp1,temp2,temp3,temp4,temp5,temp6,temp7,temp8,
temp9,temp10,temp11,temp12,temp13,temp14,temp15,temp16,temp17,temp18,
temp19,temp20;

//Store for sum
reg signed [15:0] y;

//Input buffer
reg signed [15:0] z0,z1,z2,z3,z4,z5,z6,z7,z8,z9,z10,z11,z12,z13,z14,z15,
z16,z17,z18,z19,z20;

//Set up FIR coefficients generated with SciPy
assign b[   0   ]=16'h  00F1    ;
assign b[   1   ]=16'h  0135    ;
assign b[   2   ]=16'h  01FA    ;
assign b[   3   ]=16'h  032C    ;
assign b[   4   ]=16'h  04AE    ;
assign b[   5   ]=16'h  065A    ;
assign b[   6   ]=16'h  0806    ;
assign b[   7   ]=16'h  0989    ;
assign b[   8   ]=16'h  0ABB    ;
assign b[   9   ]=16'h  0B80    ;
assign b[   10  ]=16'h  0BC4    ;
assign b[   11  ]=16'h  0B80    ;
assign b[   12  ]=16'h  0ABB    ;
assign b[   13  ]=16'h  0989    ;
assign b[   14  ]=16'h  0806    ;
assign b[   15  ]=16'h  065A    ;
assign b[   16  ]=16'h  04AE    ;
assign b[   17  ]=16'h  032C    ;
assign b[   18  ]=16'h  01FA    ;
assign b[   19  ]=16'h  0135    ;
assign b[   20  ]=16'h  00F1    ;


//Resets anbd runs counter
always @ (posedge clk)  
begin
if(reset)   
coeff_add<=8'd0; 
else if(coeff_add==8'd194)  
coeff_add<=8'd1;    
else if(valid)  
coeff_add<=coeff_add + 1'd1; 
end

//Reset and record data
always @ (posedge clk)  
begin   
if(reset)   
begin
temp0<=16'd0;temp1<=16'd0;temp2<=16'd0;temp3<=16'd0;temp4<=16'd0;
temp5<=16'd0;temp6<=16'd0;temp7<=16'd0;temp8<=16'd0;temp9<=16'd0;
temp10<=16'd0;temp11<=16'd0;temp12<=16'd0;temp13<=16'd0;temp14<=16'd0;
temp15<=16'd0;temp16<=16'd0;temp17<=16'd0;temp18<=16'd0;temp19<=16'd0;
temp20<=16'd0;

y<=16'd0;   //reset y

//reset stored values
z0<=16'd0;z1<=16'd0;z2<=16'd0;z3<=16'd0;z4<=16'd0;z5<=16'd0;
z6<=16'd0;z7<=16'd0;z8<=16'd0;z9<=16'd0;z10<=16'd0;z11<=16'd0;
z12<=16'd0;z13<=16'd0;z14<=16'd0;z15<=16'd0;z16<=16'd0;z17<=16'd0;
z18<=16'd0;z19<=16'd0;z20<=16'd0;
end

//Move data through buffer on each cycle
else if(valid)  
begin   
z0<=x;z1<=z0;z2<=z1;z3<=z2;z4<=z3;z5<=z4;z6<=z5;
z7<=z6;z8<=z7;z9<=z8;z10<=z9;z11<=z10;z12<=z11;
z13<=z12;z14<=z13;z15<=z14;z16<=z15;z17<=z16;
z18<=z17;z19<=z18;z20<=z19;

//Do multiplications
temp0<=z0 * b[0]; 
temp1<=z1 * b[1]; 
temp2<=z2 * b[2]; 
temp3<=z3 * b[3]; 
temp4<=z4 * b[4]; 
temp5<=z5 * b[5]; 
temp6<=z6 * b[6];
temp7<=z7 * b[7]; 
temp8<=z8 * b[8];
temp9<=z9 * b[9];   
temp10<=z10 * b[10]; 
temp11<=z11 * b[11]; 
temp12<=z12 * b[12]; 
temp13<=z13 * b[13]; 
temp14<=z14 * b[14]; 
temp15<=z15 * b[15]; 
temp16<=z16 * b[16]; 
temp17<=z17 * b[17]; 
temp18<=z18 * b[18]; 
temp19<=z19 * b[19]; 
temp20<=z20 * b[20]; 


//Do sum
y<= temp0[30:15] + temp1[30:15] + temp2[30:15] +temp3[30:15] + temp4[30:15] + temp5 [30:15] + temp6[30:15] + temp7[30:15] + 
temp8[30:15] + temp9[30:15] + temp10 [30:15] + temp11[30:15] + temp12[30:15] + temp13[30:15] + temp14[30:15] + temp15 [30:15] +
temp16[30:15] + temp17[30:15] + temp18[30:15] + temp19[30:15] + temp20 [30:15]; 
end 
end 

assign d_out=y; //Set output to sum
endmodule

EDIT: So, I've been recommended a CIC filter. Does anyone have any insight into how I would implement this in verilog? How do I find the appropriate parameters?

\$\endgroup\$
8
  • \$\begingroup\$ Maybe a dsp.stackexchange.com has better answer. \$\endgroup\$ Commented Aug 12, 2016 at 12:41
  • 1
    \$\begingroup\$ Especially if starting at a higher sample rate, typically you use a CIC filter for the first decimation stage, and then use a polyphase FIR filter to get to the final desired sample rate and bandwidth. They point of a polyphase filter is that if you have an FIR that is also doing decimation, you only bother to do the calculations that contribute to outputs you keep - so you can actually have bigger filters than you think you could, because you only have to do the fraction of the computation that will matter. \$\endgroup\$ Commented Aug 12, 2016 at 12:50
  • \$\begingroup\$ @ChrisStratton So what you are saying is to have multiple filters? I'll have to read up on CIC filters. \$\endgroup\$
    – Dan Booker
    Commented Aug 12, 2016 at 13:02
  • 3
    \$\begingroup\$ All things being equal, I would seriously consider adding a hardware LPF to the signal chain. \$\endgroup\$
    – pgvoorhees
    Commented Aug 12, 2016 at 13:49
  • 1
    \$\begingroup\$ You have decades of room between DC and your signal. Sounds like a job for an analog filter to me \$\endgroup\$ Commented Aug 13, 2016 at 1:41

2 Answers 2

0
\$\begingroup\$

The best option here is not just to filter the signal, but also to decimate it at the same time. There are various filter architectures which can do this efficiently, such as a polyphase FIR filter, a CIC filter, or some combination. If you're only interested in the DC part, you can probably get away with just a CIC filter. If you want to get a flatter frequency response out of a CIC filter, then a combination of CIC and polyphase FIR would be a good option. CIC filters are extremely efficient to implement on an FPGA. See https://github.com/alexforencich/verilog-dsp/blob/master/rtl/cic_decimator.v for an example of a parametrizable CIC decimator. I think the most common parameters are M=1 and N=2, then set RMAX and the rate input accordingly.

\$\endgroup\$
12
  • \$\begingroup\$ This looks great. Do you have any idea as to what sort of values RMAX and rate should take? And is this just a decimator or does it low pass as well? \$\endgroup\$
    – Dan Booker
    Commented Aug 15, 2016 at 13:59
  • \$\begingroup\$ RMAX is just the maximum rate that you want to use. It sets the register widths so that you don't get overflow that that rate. Then you connect the rate input to the decimation rate that you want. You can change the rate at run time if you want, or just tie it to a constant value. This is CIC low pass and decimation, so you may want to follow it up with an FIR low pass filter to get a sharper profile, to correct for the CIC filter profile, or both. \$\endgroup\$ Commented Aug 15, 2016 at 14:27
  • \$\begingroup\$ And since the output is decimated, you could follow it up with a long FIR filler that's implemented with a single multiplier and a couple of RAMs. \$\endgroup\$ Commented Aug 15, 2016 at 14:29
  • \$\begingroup\$ Running the module with default parameters and a 1MHz input on my testbench seems to just return the same wave with phase delay. \$\endgroup\$
    – Dan Booker
    Commented Aug 15, 2016 at 14:40
  • \$\begingroup\$ Try setting RMAX and rate to something large. Say, 100. \$\endgroup\$ Commented Aug 15, 2016 at 14:42
1
\$\begingroup\$

This is an old post, but since others may be reading it, here are some comments: 1. There is no such thing as only wanting the DC component of a signal. The DC component of a signal is a constant voltage, therefore does not need to be measured! OK, I'm straining at a gnat, actually the OP probably wants to measure the slowly varying DC value--but the point is that it is slowly varying. Even at power up it is going to jump from 0 to some value. How long a delay is acceptable for seeing that jump. Answer that question and you know the cutoff frequency of the intended filter. While it may be counter-intuitive, any signal worth measuring has some cutoff frequency.

  1. If the signal needing filtering has components up to 100 MHz, then either they must be removed in hardware, or the signal must be sampled at >= 200 MHz. Even if only (near) DC values are of interest it is possible that a frequency component could land on or very near 2 * the Nyquist frequency and alias to 0, changing the DC value being measured.

Since in this case only (near) DC values were needed, the first step is to design a hardware filter. Even if it is a simple RC network, it could remove a lot of things that might alias. Then two numbers need to be determined: 1) the highest frequency that can pass through that filter with sufficient amplitude to cause problems and 2) the fastest rate of change (aka highest frequency) of the desired DC voltage. The first number determines the cutoff frequency of the filter. For optimal design, this isn't just a number, but a slope that the digital filter must complement in order to insure that these signals don't get into the output. Also it determines the required sampling rate. And speaking of that, it is OK if the sampling rate produces a Nyquist frequency below this PROVIDED that nothing aliases into the region that must pass through the digital filter. A lot of designers over-design because they forget that a digital filter will filter out aliased frequencies as well as non-aliased ones. The second number indicates the passband requirement of the filter. If the system needs to respond to changes in the DC within 1/10 of a second, then the passband must extend out to 10 Hz.

And yes, a CIC filter would be an ideal solution to this problem. The OP indicated that the lowest signal that needed to be removed was 100 kHz. If we assume that the highest frequency that needed to be preserved was 10 Hz, there is a wide transition band between these two. CIC filter's largest downfall is that they tend to roll off higher frequencies in the passband. But there is a lot of room between 10 Hz and 100 kHz to set a corner frequency that will significantly attenuate 100 kHz without compromising 10 Hz.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.