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
- xilinx Spartan 6 DFT 设计笔记
- Xilinx Spartan 6 管脚分配
- Xilinx Spartan-6 配置SPI FLASH启动
- Xilinx Spartan 6 驱动ADS1278/4
- Xilinx spartan-3 封装
- 《Xilinx可编程逻辑器件设计与开发(基础篇)》连载49:Spartan-6的PicoBlaze性能
- 《Xilinx可编程逻辑器件设计与开发(基础篇)》连载21:Spartan-6的时钟资源
- 《Xilinx可编程逻辑器件设计与开发(基础篇)》连载21:Spartan-6的时钟资源
- 《Xilinx可编程逻辑器件设计与开发(基础篇)》连载13:Spartan-6的BRAM(Block RAM)模块
- xilinx Spartan 6 FPGA 配置 SPI Flash 芯片
- XILINX基础知识2(XILINX Spartan 6 FPGA 配置 SPI Flash 芯片)
- 基于Xilinx Spartan 3E-1200开发板的多功能秒表设计
- xilinx FPGA 串口设计笔记
- Xilinx FPGA开发板 Digilent Spartan-3E 学习资料
- xilinx fpga 学习笔记6:行为仿真
- 基于Xilinx Spartan 3E-1200开发板的VGA多模式 彩条发生器
- Xilinx FPGA开发板 Digilent Spartan-3E 学习资料整理
- Xilinx FPGA开发板 Digilent Spartan-3E 学习资料收集整理
- 自适应页面布局使得应用适应不同屏幕的尺寸变得更加容易
- SNART方法
- hdu2089数位dp
- C++中的const使用小结
- xamarin 破解
- xilinx Spartan 6 DFT 设计笔记
- Android混合开发之Activity类与html页面之间的相互跳转之PhoneGap
- 中级篇——并查集
- 字符转为Date,Date转为Long
- 拉格朗日乘子法(Lagrange Multiplier) 和KKT条件
- PHP 里面的数组按照某个时间字段排序
- css3 特性
- LeetCode之Longest Substring Without Repeating Characters
- Attribute is missing the Android namespace prefix错误