您的位置:首页 > 其它

xilinx Spartan 6 DFT 设计笔记

2015-09-01 09:44 417 查看
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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: