EDA实验II
信号源、频率计的设计
姓名:叶爽
班级:08042100
学号:0804210147
指导教师:花汉兵 蒋立平
摘要
本实验利用ALTERA公司的CYCLONE系列硬件平台和QUARTUSⅡ软件平台,对Cyclone系列的EP1C12Q240C8芯片进行设计,实现了一个简单的正弦波,三角波及调幅波信号源和频率计。
关键字:
FPGA QURATUSⅡ 数字钟 VHDL
In this experiment, we use the hardware platform of cyclone produced by ALTERA and the software platform which called quartus II.We design a simple sine wave and triangle wave and amplitude-modulation wave generator on the chip of ep1c12q240c8 of cyclone series.
Keyword: FPGA QURATUS Ⅱ VHDL, Signal Generator.
实验目的 P4
实验要求 P4
实验步骤及设计过程 P4
编译下载 P13
经验总结 P13
一.实验目的。
1. 熟悉QUARTEUS-Ⅱ软件的使用方法,和实现FPGA电路设计的一般流程。
2. 了解VHDL语言编程。
3. 了解基本的自顶向下模块化设计思想。
二.实验要求。
1.利用DDS的方案,产生一个正弦波输出,频率可控。
2.利用DDS产生调制信号和载波信号,并根据所学的高频电子线路知识产生标准调幅波。
3.设计一个数字频率计,能够将频率显示在数码管上。
三、实验步骤及设计过程。
1.DDS信号发生模块(半周期数据,正弦波,三角波)(模块名:)
DDS其实质是以基准频率源(系统时钟)对相位进行等间隔的采样。由图见,DDS 由相位累加器和波形存储器(即,ROM查询表)构成的数控振荡器(NCO_ Numerically Controlled Oscillators)、数模转换器(DAC)组成。在每一个时钟周期,N位相位累加器与其反馈值进行累加,其结果的高L位作为查询表的地址,然后从ROM中读出相应的幅度值送到DAC。再由DAC将其转换成为阶梯模拟波形,最后由具有内插作用的LPF将其平滑为连续的正弦波形作为输出。因此,通过改变频率控制字K就可以改变输出频率。
DDS模块中,遇到的第一个问题就是如何产生有符号数的ROM。在实验指导手册里提供的都是产生无符号数的计算机程序,但是由于后面涉及到调幅波中的相乘运算,使用无符号数的话会非常麻烦,因此第一步必须做出有符号数的ROM出来。产生有符号数的C++程序如下。其中加下划线的部分就是有符号数和无符号数的程序区别所在。
#include"iostream.h"
#include"stdio.h"
#include"math.h"
int main(int argc,char*argv[])
{
int i;
double s;
for(i=0;i<2048;i++)
{
s=sin(atan(1)*8*i/4096);
printf("%d:%x;\n",i,(int)(s*511));
}
return 0;
}
相位累加器,寄存器均由VHDL语言编写,比较简单,此处只给出源程序,略去分析。
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
entity phase_adder is
port(
k:in std_logic_vector(11 downto 0);
clk:in std_logic;
en:in std_logic;
reset:in std_logic;
out1:out std_logic_vector(11 downto 0)
);
end entity phase_adder;
architecture art of phase_adder is
signal temp:std_logic_vector(11 downto 0);
begin
process(clk,en,reset)
begin
if reset='1' then
temp<="000000000000";
else
if clk'event and clk='1' then
if en='1' then
temp<=temp+k;
end if;
end if;
end if;
out1<=temp;
end process;
end architecture;
==================================
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
entity reg11 is
port
(
clk:in std_logic;
d:in std_logic_vector(11 downto 0);
q:out std_logic_vector(10 downto 0)
);
end reg11;
architecture art of reg11 is
signal temp:std_logic_vector(11 downto 0);
begin
process(clk)
begin
if(clk'event and clk='1') then
if (d<"100000000000") then
temp<=d;
else temp<=("111111111111"-d);
end if;
end if;
end process;
q<=(temp(10)&temp(9)&temp(8)&temp(7)&temp(6)&temp(5)&temp(4)&temp(3)&temp(2)&temp(1)&temp(0));
end art;
======================================
在本次实验中,为了节约存储空间,我的ROM里只存储了半周期数据。但如果按照正弦函数的前半周期来保存的话,后半周期会涉及到符号位的转换,比较麻烦,因此我选择保存了余弦函数的前半周期数据。对于送进来的频率控制字K,如果在0-PI之间,那么不做变换,直接输出,如果在PI-2PI之间,那么输出2PI-K,即关于PI做了一个对称变换,由正弦波的对称性知,这样不会改变波形。分析寄存器的代码就可以发现,寄存器是12位地址输入,却只有11位地址输出,就是因为做了一次对称变换,接口宽度缩减了一半。
对于三角波,其核心就是改变ROM的内容,也就是产生MIF文件的计算机程序需要修改一下。我直接定义了一个周期为2PI,幅度为2的三角波函数TRIANGLE.那么MAIN函数中只要把SIN改为TRIANGLE就可以了。程序如下。
#include"iostream.h"
#include"stdio.h"
#include"math.h"
double triangle(double x)
{
if (0<=x&&x<=4*atan(1))
return ((2*atan(1))-x)/(2*atan(1));
else return (triangle(8*atan(1)-x));
}
int main(int argc,char*argv[])
{
int i;
double s;
for(i=0;i<2048;i++)
{
s=triangle(atan(1)*8*i/4096);
printf("%d:%x;\n",i,(int)(s*511));
}
return 0;
}
对于反相功能,其实就是在寄存器之前加一个反相模块就可以了。VHDL如下。
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
entity phase_reverse is
port(
en,clk:in std_logic;
phase_ctrl:in std_logic_Vector(11 downto 0);
dout:out std_logic_vector(11 downto 0)
);
end phase_reverse;
architecture beh of phase_reverse is
signal temp:std_logic_vector(11 downto 0);
begin
process(clk)
begin
if clk'event and clk='1' then
if en='1' then temp<=phase_ctrl+"100000000000";
else temp<=phase_ctrl;
end if;
end if;
end process;
dout<=temp;
end architecture;
============================================
仔细分析一下,就是当反相开关打开以后,相位控制字统一加上2048,正好是半个周期,因此反相。
完整的DDS模块
完整的DDS(反相)模块
DDS模块封装,KCUSTOM是自定义的频率控制字,CLK为时钟输入,DAIN为输出的正弦波信号。
2.调幅波模块
根据已学过的高频电子线路知识知道调幅波的表达式如下:
考虑到这当中用到大量的运算,如果一个一个单独定制乘法加法模块较为繁琐,因此直接使用VHDL语言编写,简洁明了。VHDL代码如下。
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
use ieee.std_logic_arith.all;
entity test is
generic(ma:integer:=7);
port (
clk:in std_logic;
sig:in signed (9 downto 0);
carry:in signed (9 downto 0);
amwave:out signed (9 downto 0)
);
end test;
architecture beh of test is
signal s:signed(3 downto 0);
signal t:signed(23 downto 0);
begin
s<=conv_signed(ma,4);
process(clk)
begin
if clk'event and clk='1' then
t<=(s*sig+3577)*carry;
amwave<=t(22 downto 13);
end if;
end process;
end beh;
========================================
在这当中最难理解的莫过于3577这个数了。在十进制中,我们加上的是1。之前接触的一直是10进制,没有仔细思考这个1的含义。现在换成2进制,当然不能加上二进制的1,而要换成别的。十进制当中,这个1是调制度M的最大值与正弦函数的最大值1相乘的结果。而在二进制中,调制度用四位有符号数表示,最大为7,正弦函数用10位有符号二进制数表示,最大值为511。因此,相乘后结果就是3577。用它来替换原来十进制中的1,才能得出正确的结果。
如图所示为AM模块的原理图
3.频率计模块
频率计的原理有两种,一种是计数式测量,一种是周期式测量。前者对与频率较高的信号测量较为准确,后者对于频率较低的信号测量较为准确。考虑到我们要测量的信号频率都很高,因此选择前者。我设计的频率计有4个量程,×1,×10,×100,×1000。原理很简单,例如×1档来说,只要给一个1HZ脉冲,通过计数器计算出在脉冲时间内信号上升沿的个数就可以了,也就是把信号的符号位接在计数器的CLK端。
TIMER模块为定时电路,控制计数的停止与开始,送到T_CON模块后,由该模块发出使能信号。CNT4*10模块为计数模块,DISPREG为显示寄存器,使得示数不至于变化的太快。
4.基准频率源模块
由于频率计模块中涉及到需要产生精确的基准频率,因此需要对系统48M频率进行分频。对48分解质因数可知,需要2,3,5分频。2,3分频的模块在数字钟里已有。5分频的原理和3分频非常类似,同样是利用把PQ变量错开半周期,不同的是判断语句不一样,而且在5分频里PQ要变为三位矢量,否则没有足够多的状态数来产生分频效果。
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
entity FP5 is
port(
clk:in std_logic;
clkout:out std_logic
);
end FP5;
architecture rtl of FP5 is
signal p,q:std_logic_vector(2 downto 0);
begin
process(clk)
begin
if (clk'event and clk='1') then
if p="100" then p<="000";
else
p<=p+1;
end if;
end if;
end process;
process(clk)
begin
if (clk'event and clk='0')
then
if q="100" then q<="000";
else
q<=q+1;
end if;
end if;
end process;
clkout<='1' when (p(2)='0' and q(2)='0' and (p(1)='0' or p(0)='0')) else '0';
end rtl;
====================================================
5.AD转换(未完成)
我在周二的时候就已经做完了所有的基本功能,然后开始思考如何加上一些附加功能。我首先考虑的就是,我的频率计能不能测量一个外部的信号的频率。由于板子内部全部是数字信号,因此外部的模拟信号进来必须要经过AD转换。将外部的信号接在板子上的SGIN引脚处,对ADC做适当的设定,在ADC的8个输出脚上就会产生相应的数字信号。于是我在接完线以后尝试使用SIGNALTAP逻辑分析仪来捕捉ADC的输出信号,但是很遗憾,尝试了一个下午,ADC输出口出来的数字信号始终无规律,而按照理论它应该是正弦波。更换实验台也无果。这个问题曾经在去年的华为杯比赛时出现过,我们为了这个问题调试了整整一天,直至最后才解决。不过我已经记不清当时是如何解决的了,当时做的报告上也没有写这方面的内容。所以,以后写报告的时候一定要有经验总结的内容,把实验中遇到的所有问题都记录下来。
6.相位测量(未完成)
相位测量的原理也是比较简单的。对于两个用DDS产生的正弦波信号,只需取出他们同一时刻的频率控制字的差值,计算其差值,再换算成相位,送到显示电路显示即可。但是很遗憾的是,我在接入相位测量电路以后,频率计的示数错误。在之前的调试过程中曾经出现过这样的状况,我知道是时钟信号太多,互相之间有干扰的原因,但是在我尽全力把所有的多余的高频率时钟去掉以后,问题仍然没有解决。由于时间已经到快验收的时间了,所以不得已只能使用做相位测量之前的备份版去验收,这个功能没有能够出现在最后的验收作品中。
7.动态显示电路
动态显示电路主要目的就是显示频率计的示数。与之前数字钟的设计几乎完全一致,只不过此次是4位数码管而已,所以对上次的电路略加修改就可以了。
8.显示切换电路
由于要在正弦调幅波,三角调幅波,反相波三者之间切换,所以我自己写了个简单的数据选择器来实现切换功能。
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
ENTITY MUX42 IS
PORT(
CLK,SW2,SW3:IN STD_LOGIC;
SIGIN,AMWAVEIN,SIG_ORI,SIG_REV,TRI_SIG,TRI_MOD:STD_LOGIC_vECTOR(9 DOWNTO 0);
DA1IN,DA2IN:OUT STD_LOGIC_VECTOR(9 DOWNTO 0)
);
END MUX42;
ARCHITECTURE BEH OF MUX42 IS
SIGNAL TEMP1,TEMP2:STD_LOGIC_VECTOR(9 DOWNTO 0);
BEGIN
PROCESS(CLK)
BEGIN
IF CLK'EVENT AND CLK='1' THEN
IF SW2='1' THEN
TEMP1<=SIG_ORI;
TEMP2<=SIG_REV;
ELSIF SW3='1' THEN
TEMP1<=SIGIN;
TEMP2<=AMWAVEIN;
ELSE
TEMP1<=TRI_SIG;
TEMP2<=TRI_MOD;
END IF;
END IF;
END PROCESS;
DA1IN<=TEMP1;
DA2IN<=TEMP2;
END BEH;
===========================================
10.其它
总图内还有很多其它的小模块比如常数模块等,比较简单,用VHDL写也就是几行的工作量,在此略去。
11.总图
三. 编译下载
首先进行引脚的设置,先对最终的xianshi电路进行编译,然后点Assignments->Pins,然后按书后附录配置引脚。
配置完再点Assignments->Settings设置其余管脚,然后再编译,点下载按钮,再点Hardware Setup将平台改为ByteBlaster[LPT1],再点START即可。
四. 实验总结和感受。
1.我在实验中遇到过的比较大的困难。
1)频率计示数错误。 其实这个问题的解决纯粹是经验了,因为单单去排查频率计和显示电路都没有任何问题。我只是联想到做电子钟的时候有一次因为多引了一个1000HZ的OUTPUT出来并且没有锁定任何端口,结果直接导致钟走时不稳定。所以我怀疑是不是还是这方面的问题。我把所有多余的时钟信号全部删掉以后,问题解决。所以SIMPLE IS BEST是永恒不变的真理,在实现同样的功能的情况下,结构简单意味着高可靠性!不仅是电路,现在的机械设计都有回归简单的趋势,都是一个道理,简单的东西维护成本低,故障率小。尤其是我电路里多出来的时钟这样多余的东西,更是不应当保留。
2)调幅波波形不对。某天下午我一直都在折腾调幅波公式里的1换算成2进制到底是多少。由于我进度比较快,也没有别人可以求助,于是我从差不多从12位2进制的0一直试到111111111111,几乎见过了所有的错误波形,没有一个是对的。所有的波形都基本上都是由调幅波做某种变换而来,都很像,但都不对。晚上回去静下心来翻了一会高频书,终于弄懂了,觉得应该是7×511。第二天上机一试,果然成功了。所以我觉得以后做实验千万不能急于求成,看到东西近在眼前却搞不定就急躁,乱来。我要是下午就翻翻书,也许下午就搞出来了,不用等晚上了。所以做实验的时候心态要好。
2.我在绘制原理图过程中的经验总结
1.使用VHDL代码写模块时,保存的VHD文件的名字最好和代码里ENTITY后的实体名保持一致,否则在你写了多个相似模块以后,QUARTUS很有可能张冠李戴,在你双击了这个模块以后它却打开了另一个VHD文件,不注意的话很容易出错。
2.连线要简洁。这个感受是我在做电子钟的时候就体会到的。对于那些密密麻麻的端口连接,直接用信号名就可以,简洁明了。
3.对于从书上看来的源代码的原理一定要弄懂。我之前电子钟里面的3分频代码是从书上看来的,当时我对它的原理不是太清楚。后来偶然的机会我用仿真的方法仔细观察了一下波形,一目了然,弄清了它的原理。于是本次实验中需要5分频的时候我就能很顺利的在源代码的基础上改一下就产生了5分频。直接抄代码的话,一旦电路出现任何问题,根本都不知道从哪下手。
4.关于创新设计。这次实验我的进度比较快,所以我有比较充裕的时间来思考一些附加功能。从实用的角度出发,我首先想到的就是,我能不能测量一个外部进来的正弦信号的频率,老是测量板子里面产生的频率我觉得有点自己和自己玩的感觉,没什么意思。后来很遗憾,卡在AD转换那一步了。然后我设计了一个三角波发生器,这个我觉得还比较实用。后来周三的晚上我曾经去图书馆查了一下有没有什么比较好的设计,感觉都不大实用,比如LCD图片显示,音乐播放等等,音乐播放也不是什么特别难的代码,但是没什么实用性,我就没加了.另外,半周期数据ROM我觉得是个好设计思路,甚至可以简化成1/4周期,因为这样的设计可以节约存储空间。现在一个周期只有4096个点,当精度要求提高,或者波形种类大幅增多以后,一半,或者1/4节省出来的存储空间就非常可观了,可以留出空间给那些需要更多ROM空间的模块。
我觉得,简单就是最好的。所有的创新设计,其目的都应当是强大一个产品的功能,而不是是它看上去更炫。一个电路设计,完成了它的功能以后,再加上若干工作指示灯,就可以了。工作指示灯是绝对必须的。在做成成品板子以后,如果你连电路的工作状态都不知道,出了问题,怎么查错?就比如实验箱上的AD/DA PACK,上面两个LED是常亮的,一个左上角的电源灯,还有一个右下角的配置成功指示灯(在配置的过程中会熄灭)。非常的实用。我也曾经拆过一些成品电子产品的板子,除了那些给用户看的指示灯以外,在板子上还有若干个用于调试的指示灯,很实用。至于那些花里胡哨的东西,我觉得就不必了吧。花里胡哨的东西加多了只会让人有山寨的感觉,想当年山寨的名字刚出现的时候,就是因为花哨的东西太多,华而不实。一切的设计都应当以实用为主。
5.遗憾之处。这次相位测量没能做进最后的电路里不得不说是一大遗憾。究其根本原因在于,时钟电路设计不合理!我一共三个模块需要用到时钟,三角波,正弦波,以及正弦波反相。这三个模块都是始终接着48M时钟在,一直工作,只不过每次只有两路送到DA芯片罢了。如此高频的时钟振荡,很难保证他们之前不相互影响。我还发现,三角波的频率如果和正弦波不一样的话,频率计也不准。原因还是同时工作会有干扰。所以,合理的设计应该是,需要工作的模块接时钟,不需要的不接。我之前一直不把这个问题当回事,直到周四意识到干扰的严重性以后才匆忙写了个时钟切换模块,但是估计是代码写的不好,这个模块压根没工作,时间也来不及了,所以就直接交了备份上去验收了。所以,对待高频振荡的时钟一定要慎之又慎!