xilinx Spartan 6 DFT 设计笔记

来源:互联网 发布:三国杀diy软件 编辑:程序博客网 时间:2024/05/16 05:18

12年写的文档,感叹文风自由彪悍、感叹年轻...........

系统工作环境:

       芯片为: xilinx Spartan 6

       软件:    ise 12.2

       目标:    对采集到的时间序列做DFT或者FFT

 一、 设计说明

最初的思路是对采集的数据做FFT,因为FFT快,存储空间小啊,而且xilinx ISE中自带了FFT的IP核,自己调用就行了,当时感觉挺美的,但是在设计中过程中发现FFT的致命伤,至少是对我来说,因为FFT是基于基2算法的,只能对序列长度为2的幂的序列做变换,如只能对长度为2、4、16、……1024序列进行FFT变换,但是设计中不能保证序列长度就是2的幂啊!!!

尴尬!!!!!

为什么这么说呢,举个例子,要对一个3Hz的方波做FFT,采样率为15Hz,那么你采集6秒钟,也就是采集了90个点吧!这时你会尴尬的发现他不是2的幂啊,那么怎么解决这个问题呢!

第一:为什么不采集128个点啊,非要采集90个点呢,这个问题我也想过,为什么不啊,原因:在采集中,要对信号进行整周期采样,否则会发生频谱泄露,

比如采集了2.5周期的信号,频谱就会泄露了,有matlab做的实验结果为证。

采样的对象:x=3*sin(2*pi*t)

 

                                                        非整周期采样                                                                                                                  整周期采样

 

第二:对信号进行整周期采样,比如还是3Hz的方波,采集90个点,这时,对其做128个点的DFT,90个点怎么做呢,补零。(90个点+38个零),但是因为对序列进行了补零的操作,频谱结构是不会发生变化的,但是能量会损失。也就是说计算出来的频率对应的幅值是不对的,有一定的衰减。有matlab实验结果为证。


                                                补零的结果                                                                                                                     没有补零的结果

鱼与熊掌,整周与补零,两者不可兼得啊!

补零、整周、补零、整周、补零、整周、补零、整周、补零、整周、补零、整周、哎,放弃!

在没有办法好偷懒的情况下,把目光转向了DFT,查看了xilinxDFT的IP核,发现其只能最高对18位的数据进行DFT的计算,而我们的AD为24位,在对精度要求苛刻的设计中,不可行啊,pass!

毛主席说:自力更生!!!!!!自己写吧!

二、DFT的理论基础

IDFT的公式:

                                    

其中:

DFT为:

                             

其中:

设计的关键在于一个乘法器,和怎么计算sin( )的值,还有一个存储数据的ram。对于理论知识的介绍就不多进行了,设计中最多做1000个点的DFT,而且对时间的要求比较宽松,所以没有对DFT的算法进行改进。

 

三、设计的框图如下:


                                                                                    图3.1

总体的说明:

1、RAM为一个双口的,一端为AD的数据输入,另一端如上图,给了乘法器做运算。

2、乘法器用了四个,spartan6的最大可以乘法位数为18位,而AD的数据位24的,无奈之下只能用两个乘法器,把24位的数据拆成2个12位的数据然后做乘法,最后再组合到一起!

3、DDS模块,输入sin的相位值,直接输出对应的幅值。

 

各个IP核的测试:

DDS:

DDS的测试目的:

在相位值输入后,需要多少个转换的时钟周期。

                                   

                                                              图3.2                                                                    图3.3


                                                                                                                     图3.4

在做测试的时候发现,DDS的输出延时是可以设置的,如图3.3.

为了开足马力,把延时周期设为:1

图3.4的仿真也是针对延时为1的情况做的。


乘法器测试:

乘法器是spartan 6自带的硬核,DSP48A

测试的目的:12bit*16bit的乘法需要的时钟周期。


                                                                                                                      图3.4

 


                                                                                                                               图3.5

图中:P=c+b*a,速度上没话说。只用了4个周期就完成了一次乘加运算!

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Ram:

测试目的:

测试数据的输出延时,即取数据的操作周期。

在测试中,对ram中地址为0、1、2、3的内存加入0、1、2、3的数据。然后再读出。


Addra:为输入地址

Dina:为写入的数据


Addrb:为读数据的地址

Doubt:为读去的数据

测试结果:对ram的读写操作都只要一个时钟周期


五、算法的执行过程:

在设计中,第一个要解决的问题:

K:表示待计算的频率

N:表示序列的长度

因为DDS的输入为16Bit的 数据,所以要对 进行转换。

 假设输入DDS的数据位:DDS_input

 则有:

 DDS_input = (ki/N)*65536

在上面的计算中,k为定值,N为定值。所以在设计中,预先算好一组k/N*65536的值,并存在数组中,直接调用,这样既节省时间又节省资源。

例:原始信号为2.5Hz的方波,采样率为150Hz,采集的序列长度为:600

现在要做K=10的计算:

=

K/N*65536=10/600=1092.266666

每次都将1092.26666进行累加。

但是会有累加的误差,为了保证误差的减少,在设计中把整数和小数部分分别存在两个计数器中,然后分别进行累计,如果小数部分有进位!则在整数部分加1。

程序:

8'd8:                         

                       begin      

                                 ram_read_addr <= 0;

                                 dsp_en_r<= 1'b0;

                                 phase_r <= phase_r +16'd1092;              //相位整数部分累计

                                 phase_decimals_r<= phase_decimals_r + 2666;  //相位小数部分累计

                       end

                       8'd9:

                       begin

                                 dft_count_r<= 0;

                                 if(phase_decimals_r>= 10000)    //如果小数部分由进位,整数部分加一

                                 begin

                                          phase_r<= phase_r + 1'b1;          

                                          phase_decimals_r= phase_decimals_r - 10000;

                                 end                             

                       end

 


 

六、DFT的整体测试

6.1 ram的写入

说明:该仿真为了测试DFT,测试的目标:2.5Hz的方波,幅值为:1000;

采样率:150Hz,采样点数为:600.

输入ram的序列为:18(1000)、37(0)、56(1000)、75(0);

程序:

reg[7:0] num;

reg[3:0] num_N;

//--------------------------------------------------------//

//function:send data to ram

//------------------------  

always @(posedge clk or negedge rst_n)

         if(!rst_n)

         begin

                   ram_write_en<= 1;

                   num<= 0;

                   num_N<= 0;

         end

         else 

         begin

                   if(num_N< 8)

                   begin

                            ram_write_addr<= ram_write_addr+ 1'b1;

                            num= num + 1'b1;

                            if(num< 18)

                                     ram_write_data<= 1000;

                            elseif(num < 37)

                                     ram_write_data<= 0;

                            elseif(num < 56)

                                     ram_write_data<= 1000;

                            elseif(num < 75)

                                     ram_write_data<= 0;

                            if(num== 75)

                            begin

                                     num<= 0;

                                     ram_write_data<= 1000;

                                     num_N<= num_N + 1'b1;

                      end

                   end

         end

以上是向RAM里写600个点的仿真图。

 

6.2整体的计算

2012.05.14日做完了大部分的仿真,但是没有更新笔记,以下是14日完成的工作。


 

参数说明:

Dft_count_s:计算一次乘加运算的循环计数器

Ram_read_addr_s:RAM的读地址

Ram_data_out_s:RAM的数据输出

Phase:相位值,即: 的值

Cosine_out: 的输出值

Sin_out: 的输出值

Sum_real_out:计算结果实部的输出值

Sum_ima_out:计算结果虚部的输出值


做仿真的顺序是按算法的执行过程。

1.      存一个2.5Hz、幅值为1000的方波存入RAM

2.      在RAM中读出序列值

3.      计算相位的输入

4.      通过DDS输出sin和cosine的值

5.      数据输入DSP,计算并累加

在实际的仿真过程中,并没有遇到大的问题,比较耗费时间的是变量太多,总是有的变量写错了,导致仿真失败,在找问题过程中花费了很多的时间,这个以后要注意了。

 

在设计中,充分利用FPGA的并行结构,为了提高效率,采用了流水线的处理方式,在DSP做乘法的同时,下一个待计算的数据也就开始在Ram里取出与相位的输出。

但是,在仿真中,发现一个问题,在DSP计算一定的周期后,DSP停止一段时间不工作,


后来发现,只是个笑话,因为在“不干活“的那一段,ram_data_out = 0;所以结果没有变化。

 

在设计中,计算了当k=10是的结果,频率为2.5HzDFT计算。


计算的最终输出为:

Sum_real_out[47:0] = 327960000;

Sum_ima_out[47:0]=6252123000;

结果很惊人,刚开始以为是错的,后来经过变换后发现,他是正确的。

在前面的计算中, 的输出值为[0:65536],而其真实值为[-1:1];所以结果应该除以32767;

Sum_real_out[47:0] =327960000/32767=10008.8503677;

Sum_ima_out[47:0]=6252123000/32767=190805.475020;

 

N=600,所以:

Sum_real_out[47:0]= Sum_real_out[47:0]*2/N=33.36283455

Sum_ima_out[47:0]= Sum_ima_out[47:0]*2/N=636.0182500

由此可以算出,频点为2.5Hz时的幅度为:636.89268,相位为:1.455312度(有很大的偏差,理论值为:0)。

为了验证结果,在matlab中做了参数一致的仿真,结果如下:


数据如下:

Sum_ima_out[47:0]= 190353.36  

Sum_real_out[47:0] =18466.192

结果可以看似一致的,但是两种都含有误差。按理论值来算得话,Sum_real_out[47:0] 应该为0。

 

 

6.3误差分析:

在计算上面的值后,发现一些问题,算法中依然存在累计误差。


按理论值来算得话,Sum_real_out[47:0] 应该为0。

而实际的结果为:327960000;

那么误差在哪!!!

相位输入的误差:在计算中,相位输入总是为整数。如图:

Phase=1092时,其实Phase=1092.25,

Phase=2184,       Phase=2184.5,

Phase=3276,       Phase=3276.75,

而计算过程中,因为数据的精度总是将小数部分舍去。那么每次的计算都将存在一定的输出误差,而且误差的都趋向为一个方向,因为小数部分的处理都是舍去,而不是4舍五入,每次的误差累计的话,就会得到一个较大的误差值。

如图:


Phase=2814与Phase=2815时,输出的结果差了3个值!

那么Phase=2814.5时的输出与Phase=2814时的输出结果相差的值在1.5左右,多次累计后,累计误差会越来越大,导致输出的结果不对。

 

处理误差的方法:

在设计中,看见有的设计用线性插值的方法。

例:phase =2814.25 计算出:Phase[2814]和Phase[2815],那么

phase[2814.25]= Phase[2814]+ (Phase[2815]- Phase[2814])*0.25,这种算法可以将计算的精度提高一个数量级,但是误差依然是单向的。

在这次的设计中,准备使用4舍5入的方法来降低误差值。

设计中的误差来源主要是因为误差的单向累加导致的。通过4舍5入后,累加误差为趋向于0,在样点足够多的情况。

例:phase [2814.25]取值为:Phase[2814],这样会导致计算的结果偏小

phase [2814.75]取值为:Phase[2815],计算的结果会偏大。

那么在多次的取值后,误差没有单向性,而误差的数学期望应该趋近于0。

实际的操作中,发现一个尴尬的问题,误差没有减少!

分析后得出结论:采样的频率是满足条件的,但是采集的序列长度为:600,也就是说只采集了10个周期。分析出频点的最小分辨率为:0.25Hz。fs/Dft(N)=150/600=0.25.

为了减小相位误差,进行了以下的实验:

采样率fs改为:15Hz,序列长度:600仿真结果如下:


计算得出:phase=0.269°。

如此可以证明,序列采集的长度也很重要!!

 

仿真就做到这了!以下是程序:

Text Bench:

module DFT_simullattion;

 

     // Inputs

     reg clk;

     reg rst_n;

     reg dft_num;

     reg frequency_num;

     reg ram_write_en;

     reg dft_start;

     reg [11:0] ram_write_data;

     reg [10:0] ram_write_addr;

     wire [7:0] dft_count_s;

     wire [10:0] ram_read_addr_s;

     wire [11:0]ram_data_out_s;

     wire[15:0] phase;

   wire[15:0] cosine_out;

     wire[15:0] sin_out;

     wire[47:0] sum_real_out;

     wire[47:0] sum_ima_out;

     wire dsp_en;

     // Instantiate the UnitUnder Test (UUT)

     DFT uut (

              .clk(clk),

              .rst_n(rst_n),

              .dft_count_s(dft_count_s),     // bus[7 : 0]

              .ram_read_addr_s(ram_read_addr_s),// Bus [10 : 0]

              .ram_data_out_s(ram_data_out_s),   // Bus [11 : 0]

              .dft_num(dft_num),

              .frequency_num(frequency_num),

              .phase(phase),

              .dsp_en(dsp_en),

              .sum_ima_out(sum_ima_out),

              .sum_real_out(sum_real_out),

              .cosine_out(cosine_out),

              .sin_out(sin_out),

              .ram_write_en(ram_write_en),

              .ram_write_addr(ram_write_addr),

              .ram_write_data(ram_write_data),

              .dft_start(dft_start)

     );

//-------------------------------------------------------//

//说明:该仿真为了测试DFT,测试的目标:2.5Hz的方波,幅值为:1000;

//采样率:150Hz,采样点数为:600.

//输入ram的序列为:18(1000)、37(0)、56(1000)、75(1000);

//------------------------

     initial begin

              // InitializeInputs

              clk = 0;

              rst_n = 0;

              dft_num = 0;

              frequency_num =0;

              dft_start = 0;

              // Wait 100 nsfor global reset to finish

              #100; 

    

              // Add stimulushere

              rst_n = 1;

     end

always #1 clk = ~clk;

 

reg[7:0] num;

reg[7:0] num_N;

//--------------------------------------------------------//

//function:send data to ram

//------------------------  

always @(posedge clk or negedge rst_n)

     if(!rst_n)

     begin

              ram_write_en<= 1;

              ram_write_addr =0;

              num <= 0;

              num_N <= 0;

              ram_write_data<=1000;

     end

     else 

     begin

              if(num_N <101)

              begin

                       ram_write_addr<= ram_write_addr+ 1'b1;

                       num<= num + 1'b1;

                       if(num< 3)

                                 ram_write_data<= 1000;

                       elseif(num < 6)

                                 ram_write_data<= 0;

                       if(num== 5)

                       begin

                                 num<= 0;

                                 ram_write_data<= 1000;

                                 num_N<= num_N + 1'b1;

                 end

              end

              else

                       if(ram_read_addr_s< 599)

                       dft_start<= 1;

                       else

                       dft_start<= 0;

     end

endmodule

 

DFT程序,其中_S表示仿真用变量。

`define debug;

module DFT(

                       `ifdefdebug

                        output[7:0] dft_count_s,

                        output[10:0] ram_read_addr_s,// Bus [10 : 0]

                        output[11:0] ram_data_out_s,   // Bus [11 : 0]

                        output[15:0] phase,

                        output[15:0] cosine_out,

                        output[15:0] sin_out,

                        output[47:0] sum_real_out,

                        output[47:0] sum_ima_out,

                        output dsp_en,

                        `endif

                        input clk,

                        input rst_n,

                        input dft_num,

                        input frequency_num,

                        input ram_write_en,

                        input[10:0] ram_write_addr, // Bus [10 : 0]

                        input[11:0] ram_write_data, // Bus [11 : 0]          

                        input dft_start

    );

reg[7:0]  dft_count_r;       //计算部骤计数器

reg[10:0] ram_read_addr_r;         //ram的读地址的寄存器

reg[15:0] phase_r;                            //相位整数部分的寄存器

reg[15:0] phase_in_r;       //相位整数数部分的寄存器

reg[7:0]  dft_num_r;         //DFT的累加的次数寄存器

reg[15:0] phase_decimals_r; //相位小数部分的寄存器

reg dsp_en_r;

//----------------调试仿真变量---------------------------//

`ifdef debug

assign dft_count_s = dft_count_r;

assign ram_read_addr_s    =       ram_read_addr_r;

assign ram_data_out_s = ram_data_out;

assign phase = phase_in_r;

assign cosine_out = cosine;

assign sin_out = sin;

//assign sum_real_out = sum_real;

//assign sum_ima_out = sum_ima;

`endif

//-----------------------

always @(posedge clk or negedge rst_n)

     if(!rst_n)

     begin

              dft_count_r <=0;

              ram_read_addr_r<= 0;

              phase_r <= 0;

              phase_decimals_r<= 0;

              dsp_en_r <=1'b1;

              dft_num_r <=0;

              phase_in_r <=0;

     end

     else

     begin

              if(dft_start ==0)                                 //DFTdon't work

              begin

                       dft_count_r<= 0;

                 ram_read_addr_r <= 0;

                       phase_r<= 0;

                       phase_decimals_r<= 0;

         dsp_en_r <= 1'b0;

                       dft_num_r<= 0;

              end

              else

              begin

                       dft_count_r<= dft_count_r + 1'b1;

                       case(dft_count_r)                                           //DFT transform

                       8'd0:

                       begin

                                 if(phase_decimals_r>= 5000)

                                 phase_in_r<= phase_r + 1'b1;

                                 else

                                 phase_in_r<= phase_r ;

                       end

                       8'd1: 

                                 dsp_en_r<= 1'b1;

                       8'd5:

                       begin 

                                 dsp_en_r<= 1'b0;                                                              

                                 phase_r<= phase_r + 16'd10922;

                                 phase_decimals_r<= phase_decimals_r + 5000;

                       end

                       8'd6:

                       begin

                                 dft_count_r<= 0;

                                 ram_read_addr_r<= ram_read_addr_r + 1'b1;

                                 if(ram_read_addr_r== 599)

                                          ram_read_addr_r<=0;

                                 if(dft_num_r== dft_num);      

                                 if(phase_decimals_r>= 10000)

                                 begin

                                          phase_r<= phase_r + 1'b1;

                                          phase_decimals_r= phase_decimals_r - 10000;

                                 end                             

                       end

                       default:;

                       endcase

              end

     end

wire[15:0] phase_in;

wire[15:0] cosine;

wire[15:0] sin;

assign  phase_in = phase_in_r;

DDS DDS (

                       .clk(clk),

                       .phase_in(phase_in),// Bus [15 : 0]

                       .cosine(cosine),// Bus [15 : 0]

                       .sine(sin)

                       ); //Bus [15 : 0]

wire [47:0] c;

wire [47:0] p;

wire dsp_en;

wire[47:0] sum_real;

wire[47:0] sum_ima;

wire[11:0] ram_data_out;

wire[10:0]  ram_read_addr;

assign dsp_en = dsp_en_r;

assign ram_read_addr = ram_read_addr_r;

DSP DSP_real (

                                                             .clk(clk),

                                                             .ce(dsp_en),

                                                             .a(cosine),// Bus [15 : 0]

                                                             .b(ram_data_out),// Bus [11 : 0]

                                                             .c(sum_real_out),// Bus [47 : 0]

                                                             .p(sum_real_out)     // Bus [47 : 0]

                                                             );

DSP DSP_ima  (

                                                             .clk(clk),

                                                             .ce(dsp_en),

                                                             .a(sin),         // Bus [15 : 0]

                                                             .b(ram_data_out),// Bus [11 : 0]

                                                             .c(sum_ima_out),      // Bus [47 : 0]

                                                             .p(sum_ima_out)       // Bus [47 : 0]

                                                             );             

ram ram (

                                                             .clka(clk),

                                                             .wea(ram_write_en),      // Bus [0 : 0]

                                                             .addra(ram_write_addr),  // Bus [10 : 0]

                                                             .dina(ram_write_data),   // Bus [11 : 0]

                                                             .clkb(clk),

                                                             .addrb(ram_read_addr),   // Bus [10 : 0]

                                                             .doutb(ram_data_out)     // Bus[11 : 0]

                                                             );// Bus [11 : 0]

                                                            

endmodule

 

 

2 0