声明:本文只对设计原理和过程作粗略的阐述,详细可以研究我贴出来的完整源代码,也可以私信交流。
若干略缩语解释:
FPGA(Field Programmable Gate Array):现场可编程逻辑门阵列
DDS(Direct Digital Synthesizer):直接数字频率合成技术
ROM(Read-Only Memory):只读存储器
DAC(Digital to Analog Convertor):数字模拟转换器
MUX(multiplexer???????):数据选择器
???????
一、设计思路:
????????DDS(Direct Digital Synthesizer)即直接数字频率合成技术,其具体原理在这就不赘述了,不了解的可以自己查阅资料。
????????利用DDS来实现信号发生器,首先就是要把波形数据存在ROM里,然后在时钟信号的控制下,用ROM的地址对ROM里的波形数据进行查表,从而将ROM里的波形数据读出,最后通过DA转换输出模拟信号的波形。
? ? ? ? 要实现波形可调,信号发生器就要能分时输出多种波形,本次设计选用了4种常用的模拟信号:正弦波、锯齿波、方波、三角波,那么就需要4个ROM来分别存储4种波形的波形数据,在写Verilog代码时,直接调用BLOCK ROM的IP核即可。
? ? ? ? 要实现波、幅、频、相可调,就需要在输入端采用某种方式来控制波形、幅度、频率和相位的改变,这个方式可以是按键、矩阵键盘、上位机等等,本次设计选择最简单的按键方式(按下时为高电平):每次按键按下时,改变波形、幅度、频率和相位。既然使用了按键,那么为了消除按键按下和松开时的机械干扰,在写Verilog代码时,按键消抖模块就必不可少。
? ? ? ? 消抖后的按键信号进入一个DDS控制模块,该模块根据相应按键的次数计算波形、幅度、频率和相位的值(本次设计可实现幅度1到15倍整数可调、频率1到50倍整数倍频、相位15度的整数倍可调),从而产生2位波形控制信号、4位幅度控制信号、6位频率控制信号、9位相位控制信号。这些信号控制ROM的地址,从而控制对ROM里的数据的读操作。
????????最后,利用一个4选1的MUX(用波形控制信号来做地址)即可实现12位数字波形数据的输出。
????????根据以上分析,作出本设计功能框图如图1所示:
?图1
? ? ? ? 由于我个人的开发板上没有DAC,所以没有设计DAC模块,不过可以在仿真时,用Vivado软件自带的仿真工具直接把12位数字波形输出显示为模拟形式就能看到模拟波形。(这里说一句Vivado牛批)
二、设计过程:
2.1 按键消抖模块:
2.1.1 基本原理:
? ? ? ? 由于机械按键里有一个复位弹簧,相当于一个欠阻尼二阶振荡系统,具有一定的惯性,所以当按键按下和松开时,会先振荡一段时间,然后才稳定到一个确定的电平。图2为理想和实际的按键电平时序图:
?图2
????????这些振荡就称为按键抖动,毫无疑问,这种意料之外的电平抖动会对逻辑造成干扰,必须滤除(消抖)。常用的按键消抖方式有硬件消抖和软件消抖,前者是在按键后加一个硬件滤波电路,如RC电路等等;后者是通过类似中断的方式,当按键按下或松开时,先经过一定时间的延时(一般为20ms),之后再判断按键的状态是否已经稳定。
????????本次设计采用软件消抖,并利用Verilog代码编写状态机来实现。具体是将按键消抖的整个过程分为4个状态:
????????状态S0:即空闲态,该状态等待按键按下,若按键按下,则跳转到S1;若按键未按下,则不跳转。
????????状态S1:该状态在按键按下时控制一个计时器进行计时,若计时器还未计到20ms,按键电平就拉低,说明产生了一次按下抖动,状态不跳转且计时器清零;若计时器计到20ms,按键电平还未拉低,说明按下已经稳定,计时器清零且状态跳转到S2。
????????状态S2:该状态等待按键松开,若按键松开,则跳转到S3;若按键未松开,则不跳转。
????????状态S3:该状态在按键松开时控制一个计时器进行计时,若计时器还未计到20ms,按键电平就拉高,说明产生了一次松开抖动,状态不跳转且计时器清零;若计时器计到20ms,按键电平还未拉高,说明松开已经稳定,计时器清零且状态跳转到S0。
? ? ? ? 上述状态机的状态转换,是由按键按下时产生的上升沿和按键松开时产生的下降沿来触发的。因此,首先就需要采集这两个边沿,本设计采用一个2位寄存器来实现边沿采集,如图3:
? ? 图3?
? ? ? ? 由上图可知,若某一时刻,D1为“0”且D0为“1”,则采集到按键输入的一个上升沿;若某一时刻,D1为“1”且D0为“0”,则采集到按键输入的一个下降沿。
? ? ? ? 至此,可以得到按键消抖状态转换图如图4(I和O代表按键输入和消抖输出的逻辑值):
图4
2.1.2 具体描述:
? ? ? ? 根据状态机,就可以用RTL语言将按键消抖模块描述出来,编写的Verilog代码如下,状态机部分采用三段式描述:
`timescale 1ms / 1ps
module KEY_filter(
input clk, //50MHz主时钟
input rst, //复位信号,高电平有效
input key_in, //输入的按键信号,按下时为高电平
output key_filter //消抖后的按键信号
);
reg [1:0] key_test; //按键边沿检测寄存器
reg key_filter_R; //消抖后的按键信号寄存器
/*************检测边沿************/
always @ (posedge clk)
key_test <= {key_test[0],key_in};
reg [1:0] c_sta = 2'd0; //现态
reg [1:0] n_sta = 2'd0; //次态
reg [19:0] cnt = 20'd0; //计时器
/**********计时器控制**********/
always @ (posedge clk or negedge rst)
begin
if (rst)
cnt <= 20'd0;
else if (cnt == 20'd999999)
cnt <= 20'd0;
else
case (n_sta)
2'd0:cnt <= 20'd0;
2'd1:
begin
if ( (key_test == 2'b10 && cnt < 20'd999999) || (key_test != 2'b10 && cnt == 20'd999999) )
cnt <= 20'd0;
else
cnt <= cnt + 1'd1;
end
2'd2:cnt <= 20'd0;
2'd3:
begin
if ( (key_test == 2'b01 && cnt < 20'd999999) || (key_test != 2'b01 && cnt == 20'd999999) )
cnt <= 20'd0;
else
cnt <= cnt + 1'd1;
end
default:;
endcase
end
/*************状态的转换***********/
always @ (posedge clk or negedge rst)
begin
if (rst)
c_sta <= 2'd0;
else
c_sta <= n_sta;
end
/*************各状态转换条件***********/
always @ (*)
begin
case(c_sta)
2'd0:
begin
if (key_test == 2'b01)
n_sta = 2'd1;
else
n_sta = 2'd0;
end
2'd1:
begin
if (key_test == 2'b10 && cnt < 20'd999999)
n_sta = 2'd1;
else if (key_test != 2'b10 && cnt == 20'd999999)
n_sta = 2'd2;
else
n_sta = 2'd1;
end
2'd2:
begin
if (key_test == 2'b10)
n_sta = 2'd3;
else
n_sta = 2'd2;
end
2'd3:
begin
if (key_test == 2'b01 && cnt < 20'd999999)
n_sta = 2'd2;
else if (key_test != 2'b01 && cnt == 20'd999999)
n_sta = 2'd0;
else
n_sta = 2'd3;
end
default:;
endcase
end
/*************各状态的输出***********/
always @ (posedge clk or negedge rst)
begin
if (rst)
key_filter_R <= 1'd0;
else
case (n_sta)
2'd0:key_filter_R <= 1'd0;
2'd2:key_filter_R <= 1'd1;
default:;
endcase
end
/*************给消抖后的按键信号赋值***********/
assign key_filter = key_filter_R;
endmodule
2.1.3 仿真测试:
? ? ? ? 编写的testbeach如下:
`timescale 1ms / 1ps
module TB_KEY_filter(
);
reg clk; //50MHz主时钟
reg rst; //复位信号,高电平有效
reg key_in; //输入的按键信号,按下时为高电平
wire key_filter; //消抖后的按键信号
KEY_filter uut_KEY_filter(
.clk(clk), //50MHz主时钟
.rst(rst), //复位信号,高电平有效
.key_in(key_in), //输入的按键信号,按下时为低电平
.key_filter(key_filter) //消抖后的按键信号
);
/**************产生50MHz时钟**************/
always #0.00001 clk = ~clk;
/**************输入初始化**************/
initial
begin
clk = 1'd0;
rst = 1'd1;
key_in = 1'd0;
/**************按键第一次按下**************/
#0.1
rst = 1'd0;
key_in = 1'd1;
/**************模拟按下时的抖动**************/
#0.1
key_in = 1'd0;
#0.2
key_in = 1'd1;
#0.1
key_in = 1'd0;
#0.2
key_in = 1'd1;
/**************按键第一次松开**************/
#50
key_in = 1'd0;
/**************模拟松开时的抖动**************/
#0.1
key_in = 1'd1;
#0.1
key_in = 1'd0;
#0.2
key_in = 1'd1;
#0.1
key_in = 1'd0;
end
endmodule
? ? ? ? 在Vivado自带的仿真器上跑出的按键消抖模块仿真结果如图5至图8所示:
图5?
? ? ? ? 由上图可以看到,在按键按下抖动期间,按键消抖模块的输出依然为“0”。
图6?
????????由上图可以看到,在按键松开抖动期间,按键消抖模块的输出依然为“1”。?
图7?
????????由上图可以看到,按键按下后,从最后一次抖动到按键消抖模块的输出变为“1”,中间的时间就是20ms。? ?
图8?
????????由上图可以看到,按键松开后,从最后一次抖动到按键消抖模块的输出变为“0”,中间的时间也是20ms。?
? ? ? ? 至此,按键消抖模块设计成功。
2.2?波形数据的存储:
2.2.1?生成COE文件:
? ? ? ? 首先要生成每种波形的数字波形数据,这可以使用MATLAB、Guagle等软件生成一个MIF文件或者COE文件的方式来实现。MIF文件和COE文件都是用来列出存储器里存放的所有数据的文件,但两者的文件格式不同。本次设计利用Guagle软件先生成各种波形的MIF文件,再将MIF文件改成COE文件,图9是生成的正弦波COE文件的一部分:
图9?
2.2.2?调用BLOCK ROM IP核:
? ? ? ? Xilinx的FPGA中具有很多不同功能的IP核,调用这些IP核可以大大提高设计效率。上面说到,本次设计利用BLOCK ROM的IP核来存放波形数据,具体是调用数据宽度为8,数据深度为512的单口ROM,IP配置如图10所示:
图10?
? ? ? ? 之后,将生成的波形COE文件,放到IP核里即完成了波形数据的存储。
2.3?DDS控制模块:?
2.3.1?波形调整:
? ? ? ? 波形调整实现是利用输入的波形控制键控制2位波形控制信号,从而改变4种输出波形。具体过程为:当波形控制键按下时,产生波形按键输入信号,该信号经过按键消抖后存入一个边沿检测寄存器(原理同按键边沿检测寄存器),每当边沿检测寄存器检测到一个上升沿,波形控制信号就加1,加到3时清零,最后,波形控制信号再去控制一个4选1的数据选择器即可实现4种输出波形的调整。?
2.3.2?幅度调整:
? ? ? ? 实现幅度调整是比较简单的,只需利用一个乘法器,把波形输出先乘上一个放大倍数再输出即可。但采用这种方式只能实现放大倍数较低的放大,因为随着放大倍数的增加,输出的位宽势必也会增加,不过在实际应用中,可以在本设计的输出端加上模拟放大器,从而实现高增益放大。本设计能实现的最高增益是15倍,而在ROM里存放的8位波形数据最大为“0xFF”,故波形输出的最大数据为“0xEF1”,故波形输出位宽应该为12位。?
????????具体过程为:当幅度控制键按下时,产生幅度按键输入信号,该信号经过按键消抖后存入一个幅度边沿检测寄存器,每当检测到一个上升沿,幅度控制信号就加1,加到15时清零,最后,每个从ROM里输出的波形数据乘上幅度控制信号即实现幅度调整。
2.3.3?相位调整:
? ? ? ? 由于存放在ROM里的波形数据是512个离散的数据点,若从地址为0的数据开始输出,那么输出的波形相位就为0°,由此不难想到,只要改变ROM的初始输出地址,就能实现相位调整。
????????设需要产生的相移为N°,则对应的相位控制信号P为:
? ? ? ? 基于以上算法,本设计实现相位15°的整数倍可调,那么每次产生的相位控制信号就应该为21的整数倍,具体过程为:当相位控制键按下时,产生相位按键输入信号,该信号经过按键消抖后存入一个相位边沿检测寄存器,每当检测到一个上升沿,相位控制信号就加21,加到504时清零,然后将原来的ROM初始输出地址赋值为新的相位控制信号,从而实现相位调整。
2.3.4?频率调整:
? ? ? ? 利用波形数据的离散性,也能实现频率调整。在一定的初始输出地址下,若每间隔一个数据点输出一次,那么输出的波形频率就为原来的2倍,依此类推,只要改变ROM数据采样的间隔,就能实现频率调整。但是,根据奈奎斯特采样定理(采样频率必须大于等于被采样信号最高频率的2倍,否则采样后信号会失真),采样间隔不可能无限增大。
????????本次设计实现频率1到50倍整数倍频。具体过程为:当频率控制键按下时,产生频率按键输入信号,该信号经过按键消抖后存入一个频率边沿检测寄存器,每当检测到一个上升沿,频率控制信号就加1,加到50时清零,然后在主时钟的控制下,每次主时钟到来,原来的ROM初始输出地址就增加一个频率控制信号,从而实现频率调整。
? ? ? ? ?综上,每次主时钟到来,ROM地址W的算法为(W1为上一时刻的地址,P为相位控制信号,F为频率控制信号):
2.4?顶层描述:
? ? ? ? 根据以上的设计思路,编写的Verilog顶层代码如下:
`timescale 1ms / 1ps
module DDS_top(
input clk, //50MHz主时钟
input rst, //复位信号,高电平有效
input W_ctrl, //波形控制键
input A_ctrl, //幅度控制键
input P_ctrl, //相位控制键
input F_ctrl, //频率控制键
output reg [11:0] wave_out //波形输出信号
);
wire W_ctrl_key; //消抖后的波形控制键
wire A_ctrl_key; //消抖后的幅度控制键
wire P_ctrl_key; //消抖后的相位控制键
wire F_ctrl_key; //消抖后的频率控制键
wire [7:0] wave_sin; //正弦波输出信号
wire [7:0] wave_Sawtooth; //锯齿波输出信号
wire [7:0] wave_square; //方波输出信号
wire [7:0] wave_Triangular; //三角波输出信号
reg [1:0] r_M; //波形边沿检测寄存器
reg [1:0] r_A; //幅度边沿检测寄存器
reg [1:0] r_P; //相位边沿检测寄存器
reg [1:0] r_F; //频率边沿检测寄存器
reg [1:0] WAVE = 2'd0; //波形控制信号,实现输出波形相位超前15度的整数倍可调
reg [3:0] AMPLITUDE = 4'd1; //幅值控制信号,实现输出波形幅值1到15整数倍可调
reg [8:0] PHASE = 9'd0; //相位控制信号,实现输出波形相位超前15度的整数倍可调
reg [5:0] FREQUENCY = 6'd1; //频率控制信号,实现输出波形频率倍频1到50倍整数可调
reg [8:0] wave_add = 9'd0; //波形数据的地址
/*****************调用按键消抖模块实现波形选择信号消抖*****************/
KEY_filter key_WAVE(
.key_filter (W_ctrl_key),
.clk (clk),
.rst (rst),
.key_in (W_ctrl)
);
/*****************调用按键消抖模块实现幅度控制字消抖*****************/
KEY_filter key_AMPLITUDE(
.key_filter (A_ctrl_key),
.clk (clk),
.rst (rst),
.key_in (A_ctrl)
);
/*****************调用按键消抖模块实现相位控制字消抖*****************/
KEY_filter key_PHASE(
.key_filter (P_ctrl_key),
.clk (clk),
.rst (rst),
.key_in (P_ctrl)
);
/*****************调用按键消抖模块实现频率控制字消抖*****************/
KEY_filter key_FREQUENCY(
.key_filter (F_ctrl_key),
.clk (clk),
.rst (rst),
.key_in (F_ctrl)
);
/*****************波形边沿检测*****************/
always @ (posedge clk)
r_M <= {r_M[0],W_ctrl_key};
/*****************幅度边沿检测*****************/
always @ (posedge clk)
r_A <= {r_A[0],A_ctrl_key};
/*****************相位边沿检测*****************/
always @ (posedge clk)
r_P <= {r_P[0],P_ctrl_key};
/*****************频率边沿检测*****************/
always @ (posedge clk)
r_F <= {r_F[0],F_ctrl_key};
/*****************波形控制*****************/
always @ (posedge clk or negedge rst)
begin
if (rst)
WAVE <= 2'd0;
else if (r_M == 2'b01)
if (WAVE == 2'd3)
WAVE <= 2'd0;
else
WAVE <= WAVE + 1'b1;
else
WAVE <= WAVE;
end
/*****************幅度控制*****************/
always @ (posedge clk or negedge rst)
begin
if (rst)
AMPLITUDE <= 6'd1;
else if (r_A == 2'b01)
if (AMPLITUDE == 4'd15)
AMPLITUDE <= 6'd1;
else
AMPLITUDE <= AMPLITUDE + 1'b1;
else
AMPLITUDE <= AMPLITUDE;
end
/*****************相位和地址控制*****************/
always @ (posedge clk or negedge rst)
begin
if (rst)
begin
PHASE <= 9'd0;
wave_add <= 9'd0;
end
else if (r_P == 2'b01)
if (PHASE == 9'd504)
PHASE <= 9'd0;
else
begin
PHASE = PHASE + 15*511/360;
wave_add = PHASE;
end
else
begin
PHASE <= PHASE;
wave_add <= wave_add + FREQUENCY;
end
end
/*****************频率控制*****************/
always @ (posedge clk or negedge rst)
begin
if (rst)
FREQUENCY <= 6'd1;
else if (r_F == 2'b01)
if (FREQUENCY == 6'd50)
FREQUENCY <= 6'd1;
else
FREQUENCY <= FREQUENCY + 1'b1;
else
FREQUENCY <= FREQUENCY;
end
/*****************输出波形*****************/
always @ (posedge clk)
begin
case (WAVE)
2'd0:wave_out <= wave_sin * AMPLITUDE;
2'd1:wave_out <= wave_Sawtooth * AMPLITUDE;
2'd2:wave_out <= wave_square * AMPLITUDE;
2'd3:wave_out <= wave_Triangular * AMPLITUDE;
default:wave_out <= 12'd0;
endcase
end
/*****************调用正弦波块ROM*****************/
sin u1 (
.clka(clk),
.ena(1'd1),
.addra(wave_add),
.douta(wave_sin)
);
/*****************调用锯齿波块ROM*****************/
Sawtooth u2 (
.clka(clk),
.ena(1'd1),
.addra(wave_add),
.douta(wave_Sawtooth)
);
/*****************调用方波块ROM*****************/
square u3 (
.clka(clk),
.ena(1'd1),
.addra(wave_add),
.douta(wave_square)
);
/*****************调用三角波块ROM*****************/
Triangular u4 (
.clka(clk),
.ena(1'd1),
.addra(wave_add),
.douta(wave_Triangular)
);
endmodule
2.5?顶层仿真测试:
? ? ? ? 编写的testbeach如下:
`timescale 1ms / 1ps
module TB_DDS_top(
);
reg clk; //系统主时钟
reg rst; //复位信号,高电平有效
reg W_ctrl; //波形控制键
reg A_ctrl; //幅度控制键
reg P_ctrl; //相位控制键
reg F_ctrl; //频率控制键
wire [11:0] wave_out; //波形输出信号
/**************模块例化**************/
DDS_top TB_UUT(
.clk(clk),
.rst(rst),
.W_ctrl(W_ctrl),
.wave_out(wave_out),
.A_ctrl(A_ctrl),
.P_ctrl(P_ctrl),
.F_ctrl(F_ctrl)
);
/**************产生50MHz时钟**************/
always #0.00001 clk = ~clk;
/**************输入初始化**************/
initial
begin
clk = 1'd0;
rst = 1'd0;
W_ctrl = 1'd0;
A_ctrl = 1'd0;
P_ctrl = 1'd0;
F_ctrl = 1'd0;
/**************4个控制键第一次按下**************/
#1
W_ctrl = 1'd1;
A_ctrl = 1'd1;
P_ctrl = 1'd1;
F_ctrl = 1'd1;
/**************4个控制键第一次松开**************/
#21
W_ctrl = 1'd0;
A_ctrl = 1'd0;
P_ctrl = 1'd0;
F_ctrl = 1'd0;
/**************4个控制键第二次按下**************/
#21
W_ctrl = 1'd1;
A_ctrl = 1'd1;
P_ctrl = 1'd1;
F_ctrl = 1'd1;
/**************4个控制键第二次松开**************/
#21
W_ctrl = 1'd0;
A_ctrl = 1'd0;
P_ctrl = 1'd0;
F_ctrl = 1'd0;
/**************4个控制键第三次按下**************/
#21
W_ctrl = 1'd1;
A_ctrl = 1'd1;
P_ctrl = 1'd1;
F_ctrl = 1'd1;
/**************4个控制键第三次松开**************/
#21
W_ctrl = 1'd0;
A_ctrl = 1'd0;
P_ctrl = 1'd0;
F_ctrl = 1'd0;
/**************4个控制键第四次按下**************/
#21
W_ctrl = 1'd1;
A_ctrl = 1'd1;
P_ctrl = 1'd1;
F_ctrl = 1'd1;
/**************4个控制键第四次松开**************/
#21
W_ctrl = 1'd0;
A_ctrl = 1'd0;
P_ctrl = 1'd0;
F_ctrl = 1'd0;
end
endmodule
? ? ? ? 顶层仿真结果如图11至图15所示:?
图11?
? ? ? ? 由上图可以看到,当4个控制键第一次按下时,波形由正弦波变为锯齿波,同时相移15°,倍频2倍,幅度放大2倍。
图12?
????????由上图可以看到,当4个控制键第二次按下时,波形由锯齿波变为方波(图中模拟波形显示的是三角波,但是根据图13所示,转为数字显示后确实是方波的波形数据,所以在这里我怀疑应该是Vivado仿真器分辨率的问题),同时相较一开始的正弦波,相移30°,倍频3倍,幅度放大3倍。
图13?
图14?
????????由上图可以看到,当4个控制键第三次按下时,波形由方波变为三角波,同时相较一开始的正弦波,相移45°,倍频4倍,幅度放大4倍。
图15?
????????由上图可以看到,当4个控制键第四次按下时,波形由三角波变回正弦波,同时相较一开始的正弦波,相移60°,倍频5倍,幅度放大5倍。
? ? ? ? 至此,整个设计完成。
|