设计一个幅值、频率、相位均可调的正弦波发生器。
读取ROM中存储的波形数据获得一个基础波形(基频),之后不断进行循环读取。
幅值——ROM中取得数据使用乘法进行放大。
相位——改变从ROM中读取时,地址的初值。
使用一个32为寄存器,将高8位作为ROM的地址位,其余24位无特殊作用,寄存器按照系统时钟进行自加,此时高8位的变化频率与用于自加的系统时钟频率有了成倍的关系,很像计数器计数频率变化但存在区别,之后会讨论到。
系统时钟为50MHz,ROM位宽为8,深度256。那么基础频率为:
\[% MathType!MTEF!2!1!+-
% feaahqart1ev3aaatCvAUfeBSjuyZL2yd9gzLbvyNv2CaerbuLwBLn
% hiov2DGi1BTfMBaeXatLxBI9gBaerbd9wDYLwzYbItLDharqqtubsr
% 4rNCHbWexLMBbXgBd9gzLbvyNv2CaeHbl7mZLdGeaGqiVu0Je9sqqr
% pepC0xbbL8F4rqqrFfpeea0xe9Lq-Jc9vqaqpepm0xbba9pwe9Q8fs
% 0-yqaqpepae9pg0FirpepeKkFr0xfr-xfr-xb9adbaqaaeGaciGaai
% aabeqaamaabaabauaajqgaG9FaceaapgGaamOzaOWaaSbaaSqaaiaa
% dkgaaeqaaOGaeyypa0ZaaSaaaeaacaaI1aGaaGimaiabgEna0kaaig
% dacaaIWaWaaWbaaSqabeaacaaI2aaaaaGcbaGaaGOmamaaCaaaleqa
% baGaaGOmaiaaisdaaaGccqGHxdaTcaaIYaGaaGynaiaaiAdaaaGaey
% ypa0JaaGimaiaac6cacaaIWaGaaGymaiaaigdacaaI2aGaaGinaiaa
% igdacaaI1aGaamisaiaadQhaaaa!5ABC!
{f_b} = \frac{{50 \times {{10}^6}}}{{{2^{24}} \times 256}} = 0.0116415Hz
\]
所以产生一个1kHz的正弦波(1kHz的ROM地址变化速度)只需要将基频放大1000/0.0116415 = 85899.34592,即扩大85899倍。
所以若想频率每次增加1kHz,在低24位每次自加85899即可,此时85899为频率控制字。
module addr_ctrl(
input clk,
input rst_n,
output [7:0] addr
);
reg [31:0] address;
assign addr = address[31:24];
always @(posedge clk,negedge rst_n)
begin
if(rst_n == 0)
address <= 32'd0;
else
//address <= address + 32'd858996; //10kHz
address <= address + 32'd85899;
end
endmodule
通过仿真可以看到准确的产生了1kHz的正弦波
//顶层
module digital_adds(
input clk,
input rst_n,
output [7:0] data
);
wire [7:0] addr;
addr_ctrl addr_ctrl_inst(
.clk (clk),
.rst_n(rst_n),.addr (addr)
);
rom1 rom1_inst (
.address ( addr ),
.clock ( clk ),
.q ( data )
);
endmodule
//测试文件
`timescale 1ns/1ps
module digital_adds_tb();
reg clk;
reg rst_n;
wire [7:0] data;
digital_adds digital_adds_inst(
.clk (clk),
.rst_n(rst_n),.data (data)
);
initial clk = 1;
always #10 clk = !clk;
initial begin //同步复位信号需要时钟上升沿检测
rst_n = 0;
#200
rst_n = 1;
#5000000$stop;
end
endmodule
系统时钟为50MHz,ROM位宽为8,深度1024。
使用计数器产生1Hz的基频,方便之后扩频计算
\[% MathType!MTEF!2!1!+-
% feaahqart1ev3aaatCvAUfeBSjuyZL2yd9gzLbvyNv2CaerbuLwBLn
% hiov2DGi1BTfMBaeXatLxBI9gBaerbd9wDYLwzYbItLDharqqtubsr
% 4rNCHbWexLMBbXgBd9gzLbvyNv2CaeHbl7mZLdGeaGqiVu0Je9sqqr
% pepC0xbbL8F4rqqrFfpeea0xe9Lq-Jc9vqaqpepm0xbba9pwe9Q8fs
% 0-yqaqpepae9pg0FirpepeKkFr0xfr-xfr-xb9adbaqaaeGaciGaai
% aabeqaamaabaabauaajqgaG9FaceaapgGaamOzaOWaaSbaaSqaaiaa
% dkgaaeqaaOGaeyypa0ZaaSaaaeaacaaIXaGaaGimamaaCaaaleqaba
% GaaGyoaaaaaOqaaiaaikdacaaIWaGaey41aqRaaGymaiaaicdacaaI
% YaGaaGinaiabgEna0kaaisdacaaI4aGaaGioaiaaisdacaaI4aaaai
% abg2da9iaaigdacaGGUaGaaGimaiaaicdacaaIWaGaaGimaiaaicda
% caaIYaGaaGynaiaaiAdacaaIWaGaaGimaiaaicdacaaI2aGaaGynai
% aadIeacaWG6baaaa!6128!
{f_b} = \frac{{{{10}^9}}}{{20 \times 1024 \times 48828}} = 1.0000025600065Hz
\]
使用1024个数据,每个为20ns,共计48848次。
则频率控制字为1000时即可产生1kHz的正弦波。
//频率控制模块
module addr_ctrl(
input clk,
input rst_n,
output reg [9:0] addr
);
reg [15:0] cnt;
always @(posedge clk,negedge rst_n)
begin
if(rst_n == 0)
cnt <= 16'd0;
else
if(16'd48848 <= cnt)
cnt <= 16'd0;
else
cnt <= cnt + 16'd1000; //频率控制字1kHz的基频
end
always @(posedge clk,negedge rst_n)
begin
if(rst_n == 0)
addr <= 10'd0;
else
if(16'd48848 <= cnt)
addr <= addr + 10'd1;
else
addr <= addr;
end
endmodule
图中可以看到,得到了977Hz的正弦波,存在着较大的误差,实际效果与理论计算严重不符,这是为什么呢?
32位寄存器使用低位向高位进位可以确保每次加的数据都产生了效果,都向高位产生了进位。
相比使用计数器产生的基频很准确但是有着非常致命的缺点:
当频率控制字不能被计数器最大值整除,即当频率控制字即将累加到计数器最大值时,由于不能整除。可能还差一点点计数器就符合判断要求了,即已经非常接近我们预设的地址变化频率了,但是仍然不满足判断标准,必须等到下一次频率控制字的累加才可以使地址加一,这里就产生了理论计算和实际情况不同的问题。
其关键就是计数器进位是严格按照条件执行的少一点会不执行而多一点会被吞掉直接置零,舍弃掉了余数,并且由于cnt的初值从零开始且每次增加1000,说以实际上计算为:
\[% MathType!MTEF!2!1!+-
% feaahqart1ev3aaatCvAUfeBSjuyZL2yd9gzLbvyNv2CaerbuLwBLn
% hiov2DGi1BTfMBaeXatLxBI9gBaerbd9wDYLwzYbItLDharqqtubsr
% 4rNCHbWexLMBbXgBd9gzLbvyNv2CaeHbl7mZLdGeaGqiVu0Je9sqqr
% pepC0xbbL8F4rqqrFfpeea0xe9Lq-Jc9vqaqpepm0xbba9pwe9Q8fs
% 0-yqaqpepae9pg0FirpepeKkFr0xfr-xfr-xb9adbaqaaeGaciGaai
% aabeqaamaabaabauaajqgaG9FaceaapgGaamOzaOWaaSbaaSqaaiaa
% dkgaaeqaaOGaeyypa0ZaaSaaaeaacaaIXaGaaGimamaaCaaaleqaba
% GaaGyoaaaaaOqaaiaaikdacaaIWaGaey41aqRaaGymaiaaicdacaaI
% YaGaaGinaiabgEna0kaacIcacaaI0aGaaGyoaiabgUcaRiaaigdaca
% GGPaGaey41aqRaaGymaiaaicdacaaIWaGaaGimaaaacqGH9aqpcaaI
% WaGaaiOlaiaaiMdacaaI3aGaaGOnaiaaiwdacaaI2aGaaGOmaiaaiw
% dacaWGibGaamOEaaaa!6290!
{f} = \frac{{{{10}^{12}}}}{{20 \times 1024 \times (49 + 1) \times 1000}} = 976.5625Hz
\]
该计算结果和仿真得到的结果相同,并且可以看到仿真保留了三位有效数字。
也就是说b法误差有两点原因:
忘记对cnt从零开始进行处理。
而低位向高位进位得到的计数器:
传统方法b还是好用一些,,,
我竟然品出了一点连续和离散的感觉出来。
理论计算:
\[% MathType!MTEF!2!1!+-
% feaahqart1ev3aaatCvAUfeBSjuyZL2yd9gzLbvyNv2CaerbuLwBLn
% hiov2DGi1BTfMBaeXatLxBI9gBaerbd9wDYLwzYbItLDharqqtubsr
% 4rNCHbWexLMBbXgBd9gzLbvyNv2CaeHbl7mZLdGeaGqiVu0Je9sqqr
% pepC0xbbL8F4rqqrFfpeea0xe9Lq-Jc9vqaqpepm0xbba9pwe9Q8fs
% 0-yqaqpepae9pg0FirpepeKkFr0xfr-xfr-xb9adbaqaaeGaciGaai
% aabeqaamaabaabauaajqgaG9FaceaapgGaamOzaOWaaSbaaSqaaiaa
% dkgaaeqaaOGaeyypa0ZaaSaaaeaacaaIXaGaaGimamaaCaaaleqaba
% GaaGymaiaaikdaaaaakeaacaaIYaGaaGimaiabgEna0kaaigdacaaI
% WaGaaGOmaiaaisdacqGHxdaTcaaI0aGaaGyoaiabgEna0kaaigdaca
% aIWaGaaGimaiaaicdaaaGaeyypa0JaaGyoaiaaiMdacaaI2aGaaiOl
% aiaaisdacaaI5aGaaGOmaiaaiodacaaI0aGaaGOnaiaaiMdacaWGib
% GaamOEaaaa!61D7!
{f_b} = \frac{{{{10}^{12}}}}{{20 \times 1024 \times 49 \times 1000}} = 996.4923469Hz
\]
可以看到经过改进后:
module addr_ctrl(
input clk,
input rst_n,
output reg [9:0] addr
);
reg [15:0] cnt;
always @(posedge clk,negedge rst_n)
begin
if(rst_n == 0)
cnt <= 16'd0;
else
if(16'd48 <= cnt) //只要取倍数就可以了,并且要注意cnt初值为零已经多循环了一次
cnt <= 16'd0;
else
cnt <= cnt + 16'd1; //频率控制字1kHz的基频
end
always @(posedge clk,negedge rst_n)
begin
if(rst_n == 0)
addr <= 10'd0;
else
if(16'd48 <= cnt)
addr <= addr + 10'd1;
else
addr <= addr;
end
endmodule
至此文章1、2部分已经完成,第三部分的整体代码见下文
作者:野客居/13tree
出处:https://www.cnblogs.com/13tree/
本文版权归作者所有,如需转载请保留此段声明。
手机扫一扫
移动阅读更方便
你可能感兴趣的文章