[FPGA]数字等精度频率计测量模块设计[门控信号为待测信号固定倍数]
文章目录
理论分析
测量实现(都是废话)
要测一个东西的长度,就要找个尺子作为基准去和其他物品相比较,尺子上的刻度就是比较得出的待测物品长度和基准长度单位的倍数关系,从而得到物品的长度。
测频率也是一样。先找一个基准频率的信号,然后用这个基准频率的信号去和待测信号相比较,得到二者的倍数关系,就可以得到待测信号的频率。
频率单位是hz,即一秒内的周期数。可以数出单位时间内基准信号和待测信号各有多少个周期,从而可以得到频率。比如1s内待测信号有
N
A
N_A
NA个周期,基准信号有
N
B
N_B
NB个周期,基准信号的频率为
f
0
f_0
f0,设待测频率为
f
x
f_x
fx,则:
1
=
N
A
×
f
x
1
=
N
B
×
f
0
1=N_A\times f_x\quad1=N_B\times f_0
1=NA×fx1=NB×f0
联立,得
f
x
=
N
A
N
B
×
f
0
f_x=\frac{N_A}{N_B}\times f_0
fx=NBNA×f0
误差分析
就像尺子一样,刻度越小,则越精确。同样一般取基准信号周期很小,则分辨力好。 f 0 f_0 f0很大,这就意味着 N B N_B NB很大,相比之下 N A N_A NA就比较小。实际上一般取晶振作为基准频率。本实验 f 0 f_0 f0为50MHz,而待测频率一般小于5MHz。
电路中是检测上升沿来计数,所以只能记整数。因为计数不精确的问题,所以会有一定的误差,这个误差的大小为 N ± 1 N\pm1 N±1,其中 N N N为 N A N_A NA或 N B N_B NB,或都有误差。
传统的测量方法是取晶振分频后得到门控信号,然后数这段时间内的待测信号的数目,从而获得待测信号的频率。这样的测量方法可以获得精确的
N
B
N_B
NB和带有
±
1
\pm1
±1误差的
N
A
N_A
NA。则误差为:
Δ
f
x
1
=
N
A
±
1
N
B
×
f
0
−
N
A
N
B
×
f
0
=
±
f
0
N
B
\Delta f_{x1}=\frac{N_A\pm1}{N_B}\times f_0-\frac{N_A}{N_B}\times f_0=\pm\frac{f_0}{N_B}
Δfx1=NBNA±1×f0−NBNA×f0=±NBf0
若我们根据待测信号来取门控信号,从而将这个
±
1
\pm1
±1的误差放在
N
B
N_B
NB上,则误差为:
Δ
f
x
2
=
N
A
N
B
±
1
×
f
0
−
N
A
N
B
×
f
0
=
N
A
N
B
−
N
A
(
N
B
±
1
)
N
B
(
N
B
±
1
)
×
f
0
=
±
N
A
N
B
(
N
B
±
1
)
×
f
0
=
±
N
A
N
B
±
1
×
f
0
N
B
=
N
A
N
B
±
1
×
Δ
f
x
1
\begin{aligned} \Delta f_{x2}&=\frac{N_A}{N_B\pm1}\times f_0-\frac{N_A}{N_B}\times f_0\\\,\\ &=\frac{N_AN_B-N_A(N_B\pm1)}{N_B(N_B\pm1)}\times f_0\\\,\\ &=\frac{\pm N_A}{N_B(N_B\pm1)}\times f_0\\\,\\ &=\pm\frac{N_A}{N_B\pm1}\times\frac{f_0}{N_B}\\\,\\ &=\frac{N_A}{N_B\pm1}\times\Delta f_{x1} \end{aligned}
Δfx2=NB±1NA×f0−NBNA×f0=NB(NB±1)NANB−NA(NB±1)×f0=NB(NB±1)±NA×f0=±NB±1NA×NBf0=NB±1NA×Δfx1
而由频率的关系知,
N
B
N_B
NB一般至少为
N
A
N_A
NA的十倍以上(至少在本实验如此)。故相比第一种方法,第二种测量方法可以极大地减小误差。
第二种方法测得的相对误差为 δ = f x 2 f x = 1 N B ± 1 \delta=\frac{f_{x2}}{f_x}=\frac{1}{N_B\pm1} δ=fxfx2=NB±11,与待测信号无关,故又称等精度法。
等精度法实现
根据被测信号来选取闸门长度和闸门起始时间,就可以在闸门内精确确定被测信号的周期数,从而获得精确的 N A N_A NA。然后用这个门控信号来数晶振的周期数,就可以获得较精确的频率。
程序设计
模块设计
根据等精度法实现方法,需要一个门控信号生成模块,一个计数模块和一个计算模块。1
待测信号输入到Gate模块生成门控信号,产生门控信号送入计数模块对晶振时钟计数,技术结果送入计算模块计算然后输出。
左侧为时钟信号,待测信号和复位信号的输入。其中复位信号使用按键输入。
在右边设置的几个端口都是方便SignalTap找信号测量用的。
代码实现
Gate门控信号生成模块
第一个always来GATE_CNT计数和生成门控信号下降沿指示信号NEG_GATE。GATE_CNT记录门控信号GATE的长度,NEG_GATE作为计数模块的使能。
这里我设置的门控信号长度为待测信号的5000个周期,以GATE_CNT计数,并在第二个always生成GATE信号。其中在生成的时候使用了多段选择,是为了生成第一个周期之前的空档等待信号稳定,并生成门控信号之间的空档来区分计数过程。
Gate
module Gate( CLK , SIGNAL , RST , GATE , NEG_GATE_f0 );
input CLK;
input SIGNAL;
input RST;
output GATE;
output NEG_GATE; // for f0 , the negedge of gate
parameter quantity = 16'b0001_0011_1000_1000; // 5000
reg GATE;
reg NEG_GATE;
reg [16-1:0] GATE_CNT;
always @ ( posedge SIGNAL or negedge RST ) begin // cnt and NEG_GATE
if (!RST) begin
GATE_CNT <= 16'b0;
NEG_GATE_f0 <= 1'b0;
end
else if (GATE_CNT == quantity + 3'b100) begin
GATE_CNT <= GATE_CNT + 1'b1;
NEG_GATE_f0 <= 1'b1;
end
else if (GATE_CNT == quantity + 4'b1111) begin
GATE_CNT <= 16'b0;
NEG_GATE_f0 <= 1'b0;
end
else begin
GATE_CNT <= GATE_CNT + 1'b1;
NEG_GATE_f0 <= 1'b0;
end
end
always @ ( posedge SIGNAL or negedge RST ) begin // gate
if (!RST)
GATE <= 1'b0;
else if (GATE_CNT <= 2'b11) // for the gap of adjacent gates
GATE <= 1'b0;
else if (GATE_CNT <= quantity + 2'b11)
GATE <= 1'b1;
else if (GATE_CNT <= quantity + 4'b1111) // for the gap of adjacent gates
GATE <= 1'b0;
else
GATE <= 1'b0;
end
endmodule
Counter计数模块
门控信号作为使能,当门控信号为高电平时计数。
此外还需要门控信号下降沿指示信号NEG_GATE来指示门控信号的结束,在门控信号结束时将计数结果输出。在输出时判断一下,计数值不为0再赋值输出。因为门控信号生成模块输出的NEG_GATE信号长度为待测信号的周期,也就意味着在下一个clk时钟时,NEG_GATE信号仍然有效,会将0赋值给SIGNAL_CNT。也导致SIGNAL_CNT仅在NEG_GATE上升沿对应的那一个clk时钟内为真正的计数值。在输出结果同时会输出一个使能信号。如果下一个模块尽可能简单,即在收到使能信号即读取数据,则使能信号和输出结果同时跳变,这样的时序并不足以让下一个模块接收到正确的信息。如图:
信号源输出为5MHz,晶振为50MHz。CNTf0READY为使能信号,NEG_GATE_CLK为门控信号下降沿指示信号,CNT为计算模块接收到的计数结果。可见计数结果虽然正确,但计算模块并没有接收到结果,输出结果也就不对了,这是因为计算模块并没有合适的时序去接收正确的计数值。
加一个SIGNAL_CNT_temp非0的判断可让计数值保持到下一个计数周期结束。如图:
Counter
module Counter( SIGNAL , RST , GATE , NEG_GATE , CNT_READY , SIGNAL_CNT );
input SIGNAL;
input RST;
input GATE;
input NEG_GATE;
output CNT_READY;
output [32-1:0] SIGNAL_CNT;
reg CNT_READY;
reg [32-1:0] SIGNAL_CNT;
reg [32-1:0] SIGNAL_CNT_temp;
always @ ( posedge SIGNAL or negedge RST ) begin
if (!RST) begin
SIGNAL_CNT_temp <= 32'b0;
SIGNAL_CNT <= 32'b0;
CNT_READY <= 1'b0;
end
else if (GATE) begin // when gate , count
SIGNAL_CNT_temp <= SIGNAL_CNT_temp + 1'b1;
CNT_READY <= 1'b0;
end
else if (NEG_GATE) begin // when gate end , count end
if ( SIGNAL_CNT_temp )
SIGNAL_CNT <= SIGNAL_CNT_temp; // until next count end , the ans will stay on bus for read
SIGNAL_CNT_temp <= 32'b0;
CNT_READY <= 1'b1;
end
end
endmodule
计算模块
接收到使能时计算输出即可。
module Calculation( EN , RST , CNT_f0 , ANS );
input EN;
input RST;
input [32-1:0] CNT_f0;
output [40-1:0] ANS;
localparam CLK_fs = 26'b0010_1111_1010_1111_0000_1000_0000;
reg [40-1:0] ANS;
always @ ( posedge EN or negedge RST ) begin
if (!RST)
ANS <= 32'b0;
else if (EN1)
ANS <= CLK_fs * 13'b1_0011_1000_1000 / CNT_f0 ;
end
endmodule
CLK_fs为50MHz。
在计算ans的时候注意顺序,先乘再除,且要留够足够的位宽。位宽不够数据会溢出。
如果先除再乘则在除的时候会产生截断误差,则信号频率最小分辨率为Gate模块设置的计数值,即5000.
测试验证
一开始太蠢了,不知道信号发生器可以直接送入GPIO,采用的AD采样。后来才直接送入GPIO进行测量。适当取方波幅值,经ADC转换后则ADC输出的最高位为方波。取ADC输出的最高位送入频率计模块进行测量。
SignalTap中ANS一栏为频率计输出结果。
实际测量时发现还是有些问题的,结果刷新需要1至2次才会比较准确。高频时几乎看不出来,但低频就会发现测量速度慢到令人发指。低频的测量时间甚至够我开一盘扫雷。
改进方向
问题
问题主要在低频段。
低频段刷新延迟过高。5kHz时已经比较明显。当100Hz时则时间更长,需要长达50s时间。并且由于前几次测得结果明显不稳定或误差较大,100Hz时需要长达两分钟左右的时间才能得到正确的测量结果。
并且测量范围也不是很理想。
改进
当刷新延时大于1s时,即信号频率小于5kHz时,可以减小门控信号中待测信号周期数。5M/5k=1000,则信号频率小于5kHz时,单周期对应时钟频率已经超过一千。此时,即使门控信号中只有一个待测信号的周期,测量误差也将小于千分之一。
- 具体实现可以写两个Gate模块,然后分别计数和计算。Gate1的参数quantity为5000,Gate2的quantity为1。Gate2先输出结果。若Gate2的结果大于5000,则选择等待Gate1计算完毕并输出Gate1测得的结果。若Gate2的结果小于5000,则直接输出结果并忽略Gate1测得的结果。这个方案的问题在于占用资源会比较多。
- 约定一个门控信号长度上限,比如0.9s。在输出门控信号时进行clk计时,如果超过0.9s,则在下一个待测信号上升沿提前结束门控信号。这个计时并不需要很精确,因为0.9s就是我们定的,只需要保证门控信号长度为待测信号周期的整数倍即可。然后对待测信号和clk同时计数,计数结果送入计算模块计算。注意这个改进相比本实验,在第二级有两个模块,要注意输出时序的细节问题。
我在写总结的时候突然发现,这个方案里只需要对clk计数,计数完计算结果输出就可以了,根本不需要将计算模块另外写。这样可以从根本上避免级联时的时序问题。(我一开始是对clk和待测信号都计数,像最后的改进方案2一样。当时想着每个模块都尽可能简单明了,同样的功能用多个同样的模块实现,所以就想用两个计数模块,把计算模块另外写。但那时并没有考虑规定门控信号时间上限的问题,所以做完之后发现并不需要对待测信号进行计数,就删掉了另一个计数模块,但遗留下了级间时序问题。) ↩︎