曾在2020年7月使用VHDL在FPGA上实现了频率计,但是那时尚未了解状态机,设计方法不够系统,现在重新用Verilog语言进行再设计
\quad
功能需求:使用计数方法进行频率测量,主要分为三个量程(0-100Hz,100-999Hz,1k-10kHz),并且具有溢出提示,小数点、单位显示和锁存功能,自动根据输入选择量程并显示
\quad
基本原理:测频率最简单的方法就是计数法,例如在1s内进行输入波计数,1s闸门脉冲后锁存的数及时频率(计数器未溢出的情况下)。此外,由于本设计是三量程,在1000计数器小于100时,改为在10s内进行计数,增加准确度。而在计数器第一次超量程时,对输入脉冲进行10分频再计数。此外,如果待测频率过低,还可以使用测周法(但在本项目中不使用)。
\quad
状态图:
\quad
值得一提的是,我们在计数结束阶段进行锁存,在显示结束阶段进行计数器清零,这样可以保证在清零之前数据已经完成了锁存
\quad 结合控制模块的状态图,我们不难根据这个理念设计系统框图
\quad
控制模块框图:
\quad
实现代码如下:
\quad
外层:
module sys_controller(
input RESET,
input CP,
input [9:0] CNT,
input FULL,
input GATE,
output [1:0]STATE,
output [1:0] RANGE,
output OF,
output LOCK,
output RECOUNT);
reg OVER;
wire CLR;
reg recount;
always@(posedge CLR,negedge CP)begin//***atuomatically goto 0 CLR->recount
if(CP==0) recount<=1'b0;
else recount<=1'b1;
end
always@(negedge GATE,posedge recount)begin
if(recount) OVER<=1'b0;
else OVER<=1'b1;
end
sys_ctrl controller_center(.RESET(RESET),
.CP(CP),
.CNT(CNT),
.FULL(FULL),
.OVER(OVER),
.STATE(STATE),
.RANGE(RANGE),
.OF(OF),
.LOCK(LOCK),
.RECOUNT(CLR));
assign RECOUNT=recount;
endmodule
\quad 内层:
//sys_ctrl for frequency detect FD_module
module sys_ctrl#(parameter S_count=2'b00,S_display=2'b01,S_start=2'b10,L=2'b00,M=2'b01,H=2'b10)
(
input RESET,
input CP,
input [9:0] CNT,
input FULL,
input OVER,
output [1:0]STATE,
output [1:0] RANGE,
output reg OF,
output reg LOCK,
output reg RECOUNT//计数器清零信号
);
reg [1:0] current_state;
reg [1:0] next_state;
reg [1:0]range;
always@(posedge CP, negedge RESET)begin
if(~RESET)begin//复位操作
current_state<=S_start;
end
else begin//cp触发
current_state<=next_state;
end
end
always@(posedge CP)begin
case(current_state)
S_start:begin
next_state<=S_count;
range<=M;
RECOUNT<=1'b0;
OF<=1'b0;
LOCK<=1'b0;
end
S_count:begin
if(OVER==1'b1)begin//如果计数器结束计数,结果可用
case(range)
L:begin
if(FULL==0)//低量程不超
begin
next_state<=S_display;
OF<=1'b0;
LOCK<=1'b1;//用于上升沿刷新数据
end
else
begin//低量程超
next_state<=S_count;
range<=M;
RECOUNT<=1'b1;//上升沿清零计数器
OF<=1'b0;
LOCK<=1'b0;
end
end
M:begin
if(FULL==0&&CNT>=100)begin
next_state<=S_display;
OF<=1'b0;
LOCK<=1'b1;//用于上升沿刷新数据
end
else if(FULL==0&&CNT<100)begin
next_state<=S_count;
range<=L;
RECOUNT<=1'b1;
OF<=1'b0;
LOCK<=1'b0;
end
else if(FULL==1)begin
next_state<=S_count;
range<=H;
RECOUNT<=1'b1;
OF<=1'b0;
LOCK<=1'b0;
end
end
H:begin
if(FULL==0&&CNT>=100)begin
next_state<=S_display;
OF<=1'b0;
LOCK<=1'b1;//用于上升沿刷新数据
end
else if(FULL==0&&CNT<100)begin
next_state<=S_count;
range<=M;
RECOUNT<=1'b1;
OF<=1'b0;
LOCK<=1'b0;
end
else if(FULL==1)begin
next_state<=S_display;
OF<=1'b1;
LOCK<=1'b1;//用于上升沿刷新数据
end
end
endcase
end
end
S_display:begin
next_state<=S_count;
RECOUNT<=1'b1;
end
endcase
end
assign STATE=current_state;
assign RANGE=range;
endmodule
\quad
RTL框图如下:
以上是频率计设计核心内容,我们再控制模块的基础上加上计数器和锁存器,进行合理的时序调整,可以得到以下这个部分功能框图,这个框图所代表的模块可以用来检验控制模块的是否能够正常工作
\quad
代码如下:
//a combie of CNT1K and sys_controller
module part1(
input F,
input GATE,
input CP,
input RESET,
output reg [9:0] CNT_disp,
output [1:0]STATE,
output [1:0] RANGE,
output OF,
output LOCK,
output RECOUNT
);
wire of,lock,recount,full;
wire [9:0] cnt;
CNT1K cnt_inst(.F_IN(F&GATE),
.CLR(recount),
.CNT(cnt),
.FULL(full));
sys_controller conrtrl_inst(.CP(CP),
.CNT(cnt),
.FULL(full),
.GATE(GATE),
.RESET(RESET),
.STATE(STATE),
.RANGE(RANGE),
.OF(of),
.LOCK(lock),
.RECOUNT(recount));
always@(posedge lock)begin
CNT_disp<=of?10'd999:cnt;
end
assign OF=of;
assign LOCK=lock;
assign RECOUNT=recount;
endmodule
\quad
RTL框图如下:
\quad
需要提出,CP是对FPGA自带时钟的中等分频,而GATE是对FPGA自带时钟的高分频,一般为1s,在低量程时为10s。CP的引入与状态机一样,大大简化了本次设计,留出了许多设计余量,大大减少了系统脉冲冲突竞争冒险的可能性。
\quad
part1验证
\quad
低频输入:结果为16.0HZ
\quad
高频输入:结果为3.20kHz
\quad
此外这里有个设计上比较巧妙的一点,代码如下
always@(posedge CLR,negedge CP)begin//***atuomatically goto 0 CLR->recount
if(CP==0) recount<=1'b0;
else recount<=1'b1;
end
\quad 这段代码把CP作为下降沿异步清零,把CLR上升沿作为触发,可以实现CLR信号提前下降,防止干扰后续模块。
\quad 其中,如果是高频时,可以将输入频率进行10分频。(简单模拟为闸门缩短为10分之一)如果是低频时,可以延长闸门时间到10倍(这里没有做)。这些功能可以由part1的输出量程信号RANGE控制协控制器进行闸门产生和输入信号分频选择,比较简单,作为part2,这里时间原因暂不说明。
\quad 至于显示与译码模块,我们可以使用扫描显示的方法,利用计数器和数据选择器,用计数器遍历数据选择器输入,使得数据选择器循环输出各个端口数据,在视觉上实现动态扫描显示。其次,需要将CNT_disp10位信号进行除法操作,分离出其三个数据位,然后译码为8端数码管的7位(小数点位先留着),最后一位是单位数码管,其数据来自RANGE量程信息,输出为H或者K。小数点(即每个数码管的第8位)也由RANGE确定。需要注意,CNT_disp之前已经经过latch锁存,而在这里小数点位和单位位需要与之前的CLOCK信号进行同步锁存,保证显示的一致性。这些都比较简单,作为part3,暂时也先不实现。