数字频率计
本设计的项目工程源码链接:数字频率计源码
功能描述
数字频率计用来测量被测信号的频率,并且本设计能根据被测信号自动切换测量档位,不同测量档位的测试精度不同,测量档位与对应的测量精度如下所示
- 第一档:测量范围
1-99999Hz
,测量精度1Hz; - 第二档:测量范围
100kHz-999.99kHz
,测量精度10Hz; - 第三档:测量范围
1MHz-9.9999MHz
,测量精度100Hz; - 第四档:测量范围
10MHz-99.999MHz
,测量精度1kHz;
原理简述
数字频率计的原理十分简单,要测量被测信号的频率,就要搞清楚频率的定义,即1s中信号的周期数。我们根据频率的定义,那么只需要测量被测信号在1s中变化的次数就可以,就是1s中的周期数。
那么如何实现测量档位的切换呢?本数字频率计最终输出5个BCD码,也就是说如果没有档位切换功能的话最多只能显示1-99999Hz
的范围;
为了实现档位的自动切换功能,我们在计数模块增加一个溢出的输出信号,如果计数模块发生溢出,说明被测信号的频率较高,在1s中变化的次数太多。那么我们就可以转换思路,不测量1s内的变化次数了,测量被测信号在0.1s中变化的次数,最终将输出的单位改为10Hz
即可;如果仍发生溢出,继续减少这个单位时间的宽度,如从0.1s切换到0.01s,输出时继续改成相应的输出单位即可。如何改变这个单位时间的宽度在后续控制模块的设计中会给大家详细介绍。请大家好好理解上面这段话,这是档位切换的精髓,也是后续状态机设计的基础。
模块设计
顶层框图
本数字频率计从控制通路、数据通路两个方向展开设计;
控制通路给数据通路提供如下信号
- 单位时间宽度的选择信号
clk_sel[1:0]
; - 计数器的使能信号
count_en
和清零信号clear
; - 锁存器的使能信号
latch_en
;
控制通路暴露给顶层的输出信号
- 最终的频率单位:
Hz_1
、Hz_10
、Hz_100
、Hz_1000
; - 被测信号的频率越界信号:
over_range
;
数据通路给控制通路提供如下信号
- 在当前的单位时间内计数器是否溢出的标记信号
overflow
; - 分频器输出的时钟信号
clk_div_out
,供状态机使用;
数据通路暴露给顶层的输出信号
- 被测信号的频率,位宽为20(5个BCD码)
数据通路
数据通路包括计数器模块、时钟分频模块、锁存输出模块;
计数器模块
在计数器模块使能期间(en_in = 1
),对输入的fin
信号的上升沿进行计数;复位或清零信号生效时,计数器清零。
其中计数器模块由5个BCD计数器级联组成,BCD计数器完成从1-9
的计数功能,并接受和产生进位信号,方便进行模块的级联操作。
BCD计数器的代码如下
`timescale 1ns / 1ps
//
// Engineer:@david
// Module Name: bcd_counter_10
// Description: BCD计数器,从0-9计数
//
module counter_10(
input wire en_in, //输入使能信号
input wire rst_n, //复位信号
input wire clear, //清零信号
input wire fin, //待测信号
output reg en_out, //输出使能,用于控制下一个计数器的状态,当输出使能有效时,下一个模10计数器计数加1
output reg [3:0] q //计数器的输出,4位BCD码输出
);
always@ (posedge fin or negedge rst_n) begin //输入待测信号的上升沿作为敏感信号
if(rst_n == 1'b0) begin
en_out <= 1'b0;
q <= 'd0;
end
else if(en_in == 1'b1) begin
if(q == 4'b1001) begin
q <= 4'b0;
en_out <= 1'b1;
end
else begin
q <= q + 1'b1;
en_out <= 1'b0;
end
end
else if(clear == 1'b1) begin
en_out <= 1'b0;
q <= 'd0;
end
else begin
en_out <= 1'b0;
q <= q;
end
end
endmodule
计数器模块的代码如下
`timescale 1ns / 1ps
//
// Engineer:@david
// Module Name: bcd_counter
// Description: 5个BCD计数器的级联模块,同时输出溢出信号
//
module counter(
input wire fin, //频率待测信号
input wire rst_n,
input wire en_in, //输入使能信号
input wire clear,
output [3:0] q0,
output [3:0] q1,
output [3:0] q2,
output [3:0] q3,
output [3:0] q4,
output reg overflow //计数器是否溢出
);
wire en_out_0,en_out_1,en_out_2,en_out_3;
wire en_out_overflow;
counter_10 u_counter_q0 (
.en_in ( en_in ),
.rst_n ( rst_n ),
.clear ( clear ),
.fin ( fin ),
.en_out ( en_out_0 ),
.q ( q0 )
);
counter_10 u_counter_q1 (
.en_in ( en_out_0 ),
.rst_n ( rst_n ),
.clear ( clear ),
.fin ( fin ),
.en_out ( en_out_1 ),
.q ( q1 )
);
counter_10 u_counter_q2 (
.en_in ( en_out_1 ),
.rst_n ( rst_n ),
.clear ( clear ),
.fin ( fin ),
.en_out ( en_out_2 ),
.q ( q2 )
);
counter_10 u_counter_q3 (
.en_in ( en_out_2 ),
.rst_n ( rst_n ),
.clear ( clear ),
.fin ( fin ),
.en_out ( en_out_3 ),
.q ( q3 )
);
counter_10 u_counter_q4 (
.en_in ( en_out_3 ),
.rst_n ( rst_n ),
.clear ( clear ),
.fin ( fin ),
.en_out ( en_out_overflow ),
.q ( q4 )
);
always @(posedge fin or negedge rst_n) begin
if(rst_n == 1'b0) begin
overflow <= 1'b0;
end
else begin
if(clear == 1'b1) begin
overflow <= 1'b0;
end
else if(en_out_overflow == 1'b1) begin
overflow <= 1'b1;
end
else begin
overflow <= overflow;
end
end
end
endmodule
时钟分频模块
因为我们有4个档位,所以我们要对时钟进行分频输出,需得到1Hz
、10Hz
、100Hz
和1000Hz
的时钟输出,根据clk_sel[1:0]
信号进行输出时钟的选择;本设计中默认的输入时钟频率为10MHz
;
`timescale 1ns / 1ps
//
// Engineer:@david
// Module Name: clk_div
// Description: input 10MHz clk_in
// output clk_out:1Hz clk_1;10Hz clk_10;100Hz clk_100;1000Hz clk_1000
//
module clk_div(
input clk_in, //10MHz
input rst_n,
input [1:0] clk_sel,
output reg clk_out
);
reg clk_1,clk_10,clk_100,clk_1000;
reg [31:0] cnt_1,cnt_10,cnt_100,cnt_1000;
//1Hz
always @(posedge clk_in or negedge rst_n) begin
if(rst_n == 1'b0) begin
cnt_1 <= 'd0;
clk_1 <= 1'b0;
end
else begin
if(cnt_1 == 32'd4999999) begin
cnt_1 <= 'd0;
clk_1 <= ~clk_1;
end
else begin
cnt_1 <= cnt_1 + 1'b1;
clk_1 <= clk_1;
end
end
end
//10Hz
always @(posedge clk_in or negedge rst_n) begin
if(rst_n == 1'b0) begin
cnt_10 <= 'd0;
clk_10 <= 1'b0;
end
else begin
if(cnt_10 == 32'd499999) begin
cnt_10 <= 'd0;
clk_10 <= ~clk_10;
end
else begin
cnt_10 <= cnt_10 + 1'b1;
clk_10 <= clk_10;
end
end
end
//100Hz
always @(posedge clk_in or negedge rst_n) begin
if(rst_n == 1'b0) begin
cnt_100 <= 'd0;
clk_100 <= 1'b0;
end
else begin
if(cnt_100 == 32'd49999) begin //ori = 49999
cnt_100 <= 'd0;
clk_100 <= ~clk_100;
end
else begin
cnt_100 <= cnt_100 + 1'b1;
clk_100 <= clk_100;
end
end
end
//1000Hz
always @(posedge clk_in or negedge rst_n) begin
if(rst_n == 1'b0) begin
cnt_1000 <= 'd0;
clk_1000 <= 1'b0;
end
else begin
if(cnt_1000 == 32'd4999) begin
cnt_1000 <= 'd0;
clk_1000 <= ~clk_1000;
end
else begin
cnt_1000 <= cnt_1000 + 1'b1;
clk_1000 <= clk_1000;
end
end
end
always @(*) begin
case(clk_sel)
2'b00: clk_out = clk_1;
2'b01: clk_out = clk_10;
2'b10: clk_out = clk_100;
2'b11: clk_out = clk_1000;
default:clk_out = clk_1;
endcase
end
endmodule
锁存器模块
`timescale 1ns / 1ps
//
// Engineer:@david
// Module Name: latch_freq
// Description: 锁存器,完成结果的锁存
//
module latch_freq(
input wire clk_in,
input wire rst_n,
input wire latch_en,
input wire [3:0] q0,
input wire [3:0] q1,
input wire [3:0] q2,
input wire [3:0] q3,
input wire [3:0] q4,
output reg [3:0] d0,
output reg [3:0] d1,
output reg [3:0] d2,
output reg [3:0] d3,
output reg [3:0] d4
);
always @(posedge clk_in or negedge rst_n) begin
if(rst_n == 1'b0) begin
d0 <= 'd0;
d1 <= 'd0;
d2 <= 'd0;
d3 <= 'd0;
d4 <= 'd0;
end
else if(latch_en == 1'b1) begin
d0 <= q0;
d1 <= q1;
d2 <= q2;
d3 <= q3;
d4 <= q4;
end
else begin
d0 <= d0;
d1 <= d1;
d2 <= d2;
d3 <= d3;
d4 <= d4;
end
end
endmodule
控制通路
状态机设计
其中COUNT_1
状态表示以1Hz
的单位时间对输入信号进行计数,其它同理;ONE_2_TEN
状态是指将单位时间从1Hz
切换成10Hz
,其它同理;如果COUNT_x
状态未发生溢出,则进入DONE_x
状态,表示计数完成且没溢出,打开锁存器的使能信号;LATCH_x
状态就是用来输出频率的计量单位,本来我是将DONE_x
和LATCH_x
合并为一个状态的,但是频率的单位和结果在输出时序上不一致,所以增加了LATCH_x
状态来将他们的时序保持一致;
测量结果
25Hz
250kHz
5MHz
50MHz
频率越界(500MHz)
结语
本设计采用数据通路和控制通路的思路进行设计,这是我在CPU设计中学习到的思路,发现这种思路对整体框架的把握更加到位,希望大家也能好好体会。