基于FPGA的信号发生器的设计
首先先要介绍的是本设计的主芯片是ALTERA的一款芯片,芯片名称是EP2C5T144C8N。该芯片的LE单元有4608个,PLL锁相环具有2个,IO口管脚具有142个,差分通道55个,嵌入式乘法器26个,RAM的存储容量大小是119808bits.该FPGA芯片的功耗也比较低。对于信号发生器的设计,不论芯片的类型,只要所设计的逻辑单元够用即可,还有就是内存要够。好了,话不多说。直接来开始FPGA的信号发生器设计吧。
我当时用的是Quartus-II是13.0的版本作为开发环境,设计的通信方式是最常见的SPI通信方式,该方式通信比较简单。当时我为了调这个通信的时序也花了一天时间才搞定,哎!自认为还是比较笨了。
直接上图:
该图是quartus的开发环境,大家看下,感觉和你们的也不会有太大的区别。尽管软件的版本一直在升级,可是感觉变化也不会太大。
继续上图:
这个是我所建立的文件,大概看了一下,其中.v文件是我建立的一些verilog文件,有几个特殊的文件,比如.bdf文件是顶层文件。因为采用的思想是自顶向下的设计思想。先模块化,然后顶层直接调用即可。.qip文件是调用的是quatus自带的IP核,主要是用来存放波形数据的。.stp文件是signaltep文件,主要是用来硬件仿真观察数据用的,这个很有用哦,和modelsim差不多的功能,主要区别是,一个是硬件仿真,一个是软件仿真。都可以用来分析时序。
直接上代码:
module SPI_SLAVE
#(
parameterrom_len_width=9,//ROM标的深度,同时也代表了相位控制字的位数
parameterftw_width=21,//频率控制字的位数
parameter N=8
)
(
input rst, //复位端口
input [N-1:0] txdata, //N位发送数据,CS下降沿把数据存入模块
input sclk, //spi时钟
input cs, //spi片选
input mosi, //从接收端
input ftw_en, //频率输入控制端使能
input ptw_en, //相位控制端口使能
input clk, //频率>>sclk
output reg miso, //从输出端
output [ftw_width-1:0] ftw_out, //频率控制字的位数
output [rom_len_width-1:0] ptw_out//相位控制字输出
//output [5:0] count,//测试用
//output [23:0] data_temp_done //测试用
);
reg [9-1:0] temp_rx,temp_tx;
reg negedge_cs,temp_cs;
reg data_done;
reg [5:0] count_spi;
reg [24-1:0] data_temp;//24位数据暂存寄存器
reg [ftw_width-1:0] ftw_last;
reg [rom_len_width-1:0] ptw_last;
reg [4:0] i;
always @(negedge rst or posedge sclk)//mosi receive logic
begin
if(!rst)
begin
count_spi<=0;
temp_rx<=8'd0;
data_temp<=0;
ftw_last<=0;
ptw_last<=0;
i<=0;
end
else if(cs==0)
begin
case(i)
5'd0:
begin
data_temp[24-1:0]<={data_temp[24-2:0],mosi};//串行数据左移输入
i<=i+1'b1;//翻转到下一个状态5’d1
end
5'd1:
begin
data_temp[24-1:0]<={data_temp[24-2:0],mosi};//串行数据左移输入
i<=i+1'b1;//翻转到下一个状态5’d2
end
5'd2:
begin
data_temp[24-1:0]<={data_temp[24-2:0],mosi};//串行数据左移输入
i<=i+1'b1;//翻转到下一个状态5’d3
end
5'd3:
begin
data_temp[24-1:0]<={data_temp[24-2:0],mosi};//串行数据左移输入
i<=i+1'b1;//翻转到下一个状态5’d4
end
5'd4:
begin
data_temp[24-1:0]<={data_temp[24-2:0],mosi};//串行数据左移输入
i<=i+1'b1;//翻转到下一个状态5’d5
end
5'd5:
begin
data_temp[24-1:0]<={data_temp[24-2:0],mosi};//串行数据左移输入
i<=i+1'b1;//翻转到下一个状态5’d6
end
5'd6:
begin
data_temp[24-1:0]<={data_temp[24-2:0],mosi};//串行数据左移输入
i<=i+1'b1;//翻转到下一个状态5’d7
end
5'd7:
begin
data_temp[24-1:0]<={data_temp[24-2:0],mosi};//串行数据左移输入
i<=i+1'b1;//翻转到下一个状态5’d8
end
5'd8:
begin
data_temp[24-1:0]<={data_temp[24-2:0],mosi};//串行数据左移输入
i<=i+1'b1;//翻转到下一个状态5’d9
end
5'd9:
begin
data_temp[24-1:0]<={data_temp[24-2:0],mosi};//串行数据左移输入
i<=i+1'b1;//翻转到下一个状态5’d10
end
5'd10:
begin
data_temp[24-1:0]<={data_temp[24-2:0],mosi};//串行数据左移输入
i<=i+1'b1;//翻转到下一个状态5’d11
end
5'd11:
begin
data_temp[24-1:0]<={data_temp[24-2:0],mosi};//串行数据左移输入
i<=i+1'b1;//翻转到下一个状态5’d12
end
5'd12:
begin
data_temp[24-1:0]<={data_temp[24-2:0],mosi};//串行数据左移输入
i<=i+1'b1;//翻转到下一个状态5’d13
end
5'd13:
begin
data_temp[24-1:0]<={data_temp[24-2:0],mosi};//串行数据左移输入
i<=i+1'b1;//翻转到下一个状态5’d14
end
5'd14:
begin
data_temp[24-1:0]<={data_temp[24-2:0],mosi};//串行数据左移输入
i<=i+1'b1;//翻转到下一个状态5’d15
end
5'd15:
begin
data_temp[24-1:0]<={data_temp[24-2:0],mosi};//串行数据左移输入
i<=i+1'b1;//翻转到下一个状态5’d16
end
5'd16:
begin
data_temp[24-1:0]<={data_temp[24-2:0],mosi};//串行数据左移输入
i<=i+1'b1;//翻转到下一个状态5’d17
end
5'd17:
begin
data_temp[24-1:0]<={data_temp[24-2:0],mosi};//串行数据左移输入
i<=i+1'b1;//翻转到下一个状态5’d18
end
5'd18:
begin
data_temp[24-1:0]<={data_temp[24-2:0],mosi};//串行数据左移输入
i<=i+1'b1;//翻转到下一个状态5’d19
end
5'd19:
begin
data_temp[24-1:0]<={data_temp[24-2:0],mosi};//串行数据左移输入
i<=i+1'b1;//翻转到下一个状态5’d20
end
5'd20:
begin
data_temp[24-1:0]<={data_temp[24-2:0],mosi};//串行数据左移输入
i<=i+1'b1;//翻转到下一个状态5’d21
end
5'd21:
begin
data_temp[24-1:0]<={data_temp[24-2:0],mosi};//串行数据左移输入
i<=i+1'b1;//翻转到下一个状态5’d22
end
5'd22:
begin
data_temp[24-1:0]<={data_temp[24-2:0],mosi};//串行数据左移输入
i<=i+1'b1;//翻转到下一个状态5’d23
end
5'd23:
begin
data_temp[24-1:0]={data_temp[24-2:0],mosi};//串行数据左移输入
if(!ftw_en)
begin
ftw_last[21-1:0]<=data_temp[24-4:0];
data_temp<=24'd0;
end
else if(!ptw_en)
begin
ptw_last<=data_temp[8:0];
data_temp<=24'd0;
end
i<=0;//翻转到下一个状态5’d0
end
endcase
end
/*if(flag==0)
begin
count_spi<=0;
temp_rx<=8'd0;
data_temp<=0;
ftw_last<=0;
ptw_last<=0;
flag<=1;
end
else if(cs==0&&flag==1)
begin
count_spi<=count_spi+1'd1;
temp_rx[N-1:0]<={temp_rx[N-2:0],mosi};//串行数据左移输入
case(count_spi)
6'd8:
begin
data_temp[24-1:16]<=temp_rx[7:0];//高位在前
//temp_rx=0;
end
6'd16:
begin
data_temp[16-1:8]<=temp_rx[7:0];
//temp_rx=0;
end
6'd24:
begin
data_temp[7:0]<=temp_rx[7:0];
//temp_rx=0;
if(!ftw_en)
begin
ftw_last[21-1:0]<=data_temp[24-1:3];
data_temp<=24'd0;
count_spi<=0;
end
if(!ptw_en)
begin
ptw_last<=data_temp[8:0];
data_temp<=24'd0;
end
flag<=0;
end
endcase
end*/
end
/*always @(posedge clk)//cs negedge test
begin
if((cs==0)&&(temp_cs==1))
negedge_cs<=1;
else
negedge_cs<=0;
temp_cs<=cs;
end*/
/*always @(negedge sclk or posedge negedge_cs)//miso transmitlogic
begin
if(negedge_cs==1)
temp_tx<=txdata;
else
temp_tx[N-2:0]<=temp_tx[N-1:1];
miso<=temp_tx[0];
end*/
assign ftw_out=ftw_last;
assign ptw_out=ptw_last;
//assign count=count_spi;//测试用
//assign data_temp_done=data_temp;//测试用
Endmodule
上面的模块代码是用来完成SPI数据的接收,接收数据长度是24位的,这个可以根据自己的需要进行必要的调整。
module phase_accumulator
#(
parameterrom_len_width=9,//ROM标的深度
parameterphase_acc_width=26,//相位累加器的位数
parameterftw_width=21//频率控制字的位数
)
(
input rst,
input [ftw_width-1:0] ftw,
input [rom_len_width-1:0] ptw,
input clk,
output[rom_len_width-1:0] q
);
`define ptw_enable 1
`ifdef ptw_enable
reg [rom_len_width-1:0] phase;
reg [phase_acc_width-1:0] phase1;
always @(negedge rst or posedge clk)
begin
if(!rst)
begin
phase=1'd0;
phase1=1'd0;
end
else
begin
phase1<=phase1+ftw;
phase<=phase1[phase_acc_width-1:phase_acc_width-rom_len_width]+ptw;
end
end
`else
`endif
assign q = phase;
endmodule
这部分的模块是完成相位累加器的设计,采用的是当今比较流行的DDS设计原理,
输出的q是rom表的地址,该地址根据相位累加器的累加来驱动地址的递增。不同DDS的建议可以去看下DDS的设计原理。在此,我也不嫌麻烦,将它给贴出来给你们看看。如下
令DDS时钟为,相位累加器位数为n,频率控制字的位数为m,ROM表的深度的位宽为D,相位控制字的位数为p。
再令,,。
当要设计一个DDS时,应该根据具体的性能指标来设计,从而避免不必要的资源浪费,一般设计给出的具体指标如下:
指标:频率分辨率为,相位分辨率为,要求产生信号的最高频率为,且最高相位为,每个周期点数不少于N个。
根据上面的指标,我们要求得具体的DDS时钟为,相位累加器位数为n,频率控制字的位数为m,ROM表的深度的位宽为d。
(1)其中、n、m是和频率有关的参数,可以先进行求解:
① 根据要求可以列出以下不等式:
好了,我们继续:
module div_clk
#(
parameter div =2//设置分频系数
)
(
input clk,
output signal_clk
);
/*reg [8:0] flag;
reg clk_flag;
/*always @(posedge clk)
begin
flag<=flag+1'd1;
if(flag==div)
begin
clk_flag<=~clk_flag;
flag<=0;
end
end*/
assign signal_clk=clk;
endmodule
这个是用来提供时钟分频的,分频大小可以自行设置。这里的时钟主要是用来提供给DA芯片的时钟,我用的是50M的时钟所以就没有分频了。
这个是顶层文件的设计图。
下面来让我们看看仿真图:注意,仿真时只能是针对某个模块设计进行仿真,用软件仿真时,要用tesbench文件和一个.v的实例文件。硬件仿真时,主要是用到下载器里的逻辑分析仪对数据的抓取。
硬件仿真
这个是我之前仿真的时序,记录被保存了,现在拿出来,可以看到右上角有未连接硬件的显示提醒。
软件仿真时序:
在这里说以下,用外部modlsim仿真时,注意输入信号的时序要自己给出。如果对以上仿真软件不太会使用时可以自己百度以下,相信会有大把的视频或者论坛或者文本资料的,总有一款适合你的。好了,不过多介绍了。今天就到这里吧!
希望对你们有用!再多说一句,注意时序分析。